├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Makefile ├── PROJECT ├── README.md ├── api └── v1alpha1 │ ├── actor_types.go │ ├── actor_webhook.go │ ├── groupversion_info.go │ ├── kasmcloudhost_types.go │ ├── link_types.go │ ├── link_webhook.go │ ├── provider_types.go │ ├── provider_webhook.go │ ├── types.go │ ├── webhook_suite_test.go │ └── zz_generated.deepcopy.go ├── arch.png ├── cmd └── webhook │ └── main.go ├── crates ├── apis │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── v1alpha1 │ │ ├── actor.rs │ │ ├── claims.rs │ │ ├── host.rs │ │ ├── link.rs │ │ ├── mod.rs │ │ └── provider.rs └── host │ ├── Cargo.toml │ └── src │ ├── actor.rs │ ├── handler.rs │ ├── lib.rs │ └── provider.rs ├── deploy ├── crds │ ├── kasmcloud.io_actors.yaml │ ├── kasmcloud.io_kasmcloudhosts.yaml │ ├── kasmcloud.io_links.yaml │ └── kasmcloud.io_providers.yaml ├── kasmcloud_host_daemonset.yaml ├── kasmcloud_host_default.yaml ├── kasmcloud_host_deployment.yaml ├── kasmcloud_host_rbac.yaml └── webhook │ ├── kasmcloud_webhook_manifests.yaml │ └── kasmcloud_webhook_server.yaml ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── sample.yaml └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | target/ 3 | .idea 4 | .vscode/ 5 | .DS_Store 6 | /bin/ 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kasmcloud" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [workspace.package] 9 | authors = ["The KasmCloud Team", "Iceber Gu"] 10 | categories = ["wasm"] 11 | edition = "2021" 12 | license = "Apache-2.0" 13 | repository = "https://github.com/iceber/kasmcloud" 14 | 15 | [dependencies] 16 | anyhow = { workspace = true } 17 | async-nats = { workspace = true } 18 | async-trait = { workspace = true } 19 | base64 = { workspace = true } 20 | bytes = { workspace = true } 21 | futures = { workspace = true } 22 | http = { workspace = true } 23 | k8s-openapi = { workspace = true } 24 | kube = { workspace = true } 25 | nkeys = { workspace = true } 26 | opentelemetry = { workspace = true } 27 | rmp-serde = { workspace = true } 28 | schemars = { workspace = true } 29 | serde = { workspace = true } 30 | serde_json = { workspace = true } 31 | serde_yaml = { workspace = true } 32 | serde_bytes = { workspace = true } 33 | thiserror = { workspace = true } 34 | tokio = { workspace = true } 35 | tracing = { workspace = true } 36 | tracing-opentelemetry = { workspace = true } 37 | ulid = { workspace = true } 38 | url = { workspace = true } 39 | uuid = { workspace = true } 40 | wascap = { workspace = true } 41 | wasmcloud-compat = { workspace = true } 42 | wasmcloud-control-interface = { workspace = true } 43 | wasmcloud-core = { workspace = true } 44 | wasmcloud-host = { workspace = true } 45 | wasmcloud-runtime = { workspace = true } 46 | wasmcloud-tracing = { workspace = true } 47 | kasmcloud-apis = { workspace = true } 48 | kasmcloud-host = { workspace = true } 49 | config = "0.13.3" 50 | serde_derive = "1.0.188" 51 | clap = { version = "4.4.2", features = [ 52 | "color", 53 | "derive", 54 | "env", 55 | "error-context", 56 | "help", 57 | "std", 58 | "suggestions", 59 | "usage", 60 | ] } 61 | chrono = { workspace = true } 62 | names = "0.14.0" 63 | lazy_static = "1.4.0" 64 | 65 | 66 | [workspace] 67 | members = [] 68 | 69 | [workspace.dependencies] 70 | anyhow = "1.0.75" 71 | async-nats = "0.31.0" 72 | async-trait = "0.1.73" 73 | base64 = "0.21.3" 74 | bytes = "1.4.0" 75 | futures = "0.3.28" 76 | hex = "0.4" 77 | http = "0.2.9" 78 | k8s-openapi = { version = "0.19.0", features = ["schemars", "v1_27"]} 79 | kube = { version = "0.85.0", features = ["gzip", "jsonpatch", "runtime", "derive", "unstable-runtime"] } 80 | nkeys = "0.3.2" 81 | opentelemetry = "0.20.0" 82 | rmp-serde = "1.1.2" 83 | schemars = "0.8.13" 84 | serde = "1.0.188" 85 | serde_json = "1.0.105" 86 | serde_yaml = "0.9.25" 87 | sha2 = "0.10" 88 | thiserror = "1.0.47" 89 | tokio = { version = "1.32.0", features = ["full"] } 90 | tracing = "0.1.37" 91 | tracing-opentelemetry = "0.21.0" 92 | ulid = "1.0.1" 93 | url = "2.4.1" 94 | uuid = "1.4.1" 95 | wascap = "0.11.1" 96 | wasmcloud-compat = { git = "https://github.com/wasmcloud/wasmCloud", rev = "cdbc2fc99ef7a02efdc0fe4b8884247c20b2f314" } 97 | wasmcloud-core = { git = "https://github.com/wasmcloud/wasmCloud", rev = "cdbc2fc99ef7a02efdc0fe4b8884247c20b2f314" } 98 | wasmcloud-host = { git = "https://github.com/wasmcloud/wasmCloud", rev = "cdbc2fc99ef7a02efdc0fe4b8884247c20b2f314" } 99 | wasmcloud-runtime = { git = "https://github.com/wasmcloud/wasmCloud", rev = "cdbc2fc99ef7a02efdc0fe4b8884247c20b2f314" } 100 | wasmcloud-tracing = { git = "https://github.com/wasmcloud/wasmCloud", rev = "cdbc2fc99ef7a02efdc0fe4b8884247c20b2f314" } 101 | wasmcloud-control-interface = "0.28.1" 102 | kasmcloud-apis = { path = "./crates/apis" } 103 | kasmcloud-host = { path = "./crates/host" } 104 | serde_bytes = "0.11.12" 105 | chrono = "0.4.29" 106 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Image URL to use all building/pushing image targets 3 | IMG ?= controller:latest 4 | # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. 5 | ENVTEST_K8S_VERSION = 1.27.1 6 | 7 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 8 | ifeq (,$(shell go env GOBIN)) 9 | GOBIN=$(shell go env GOPATH)/bin 10 | else 11 | GOBIN=$(shell go env GOBIN) 12 | endif 13 | 14 | # CONTAINER_TOOL defines the container tool to be used for building images. 15 | # Be aware that the target commands are only tested with Docker which is 16 | # scaffolded by default. However, you might want to replace it to use other 17 | # tools. (i.e. podman) 18 | CONTAINER_TOOL ?= docker 19 | 20 | # Setting SHELL to bash allows bash commands to be executed by recipes. 21 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 22 | SHELL = /usr/bin/env bash -o pipefail 23 | .SHELLFLAGS = -ec 24 | 25 | .PHONY: all 26 | all: build 27 | 28 | ##@ General 29 | 30 | # The help target prints out all targets with their descriptions organized 31 | # beneath their categories. The categories are represented by '##@' and the 32 | # target descriptions by '##'. The awk commands is responsible for reading the 33 | # entire set of makefiles included in this invocation, looking for lines of the 34 | # file as xyz: ## something, and then pretty-format the target and help. Then, 35 | # if there's a line with ##@ something, that gets pretty-printed as a category. 36 | # More info on the usage of ANSI control characters for terminal formatting: 37 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 38 | # More info on the awk command: 39 | # http://linuxcommand.org/lc3_adv_awk.php 40 | 41 | .PHONY: help 42 | help: ## Display this help. 43 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 44 | 45 | ##@ Development 46 | 47 | .PHONY: manifests 48 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 49 | $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=deploy/crds 50 | 51 | .PHONY: generate 52 | generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 53 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 54 | 55 | .PHONY: fmt 56 | fmt: ## Run go fmt against code. 57 | go fmt ./... 58 | 59 | .PHONY: vet 60 | vet: ## Run go vet against code. 61 | go vet ./... 62 | 63 | .PHONY: test 64 | test: manifests generate fmt vet envtest ## Run tests. 65 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out 66 | 67 | ##@ Build 68 | 69 | .PHONY: build 70 | build-webhook: manifests generate fmt vet ## Build manager binary. 71 | go build -o bin/webhook cmd/webhook/main.go 72 | 73 | ##@ Build Dependencies 74 | 75 | ## Location to install dependencies to 76 | LOCALBIN ?= $(shell pwd)/bin 77 | $(LOCALBIN): 78 | mkdir -p $(LOCALBIN) 79 | 80 | ## Tool Binaries 81 | KUBECTL ?= kubectl 82 | CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen 83 | 84 | ## Tool Versions 85 | CONTROLLER_TOOLS_VERSION ?= v0.12.0 86 | 87 | .PHONY: controller-gen 88 | controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten. 89 | $(CONTROLLER_GEN): $(LOCALBIN) 90 | test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ 91 | GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) 92 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: kasmcloud.io 6 | layout: 7 | - go.kubebuilder.io/v4 8 | projectName: kasmcloud 9 | repo: github.com/wasmcloud/kasmcloud 10 | resources: 11 | - api: 12 | crdVersion: v1 13 | namespaced: true 14 | controller: false 15 | domain: kasmcloud.io 16 | kind: Actor 17 | path: github.com/wasmcloud/kasmcloud/api/v1alpha1 18 | version: v1alpha1 19 | webhooks: 20 | defaulting: true 21 | validation: true 22 | webhookVersion: v1 23 | - api: 24 | crdVersion: v1 25 | namespaced: true 26 | controller: false 27 | domain: kasmcloud.io 28 | kind: Provider 29 | path: github.com/wasmcloud/kasmcloud/api/v1alpha1 30 | version: v1alpha1 31 | webhooks: 32 | defaulting: true 33 | validation: true 34 | webhookVersion: v1 35 | - api: 36 | crdVersion: v1 37 | namespaced: true 38 | controller: false 39 | domain: kasmcloud.io 40 | kind: Link 41 | path: github.com/wasmcloud/kasmcloud/api/v1alpha1 42 | version: v1alpha1 43 | webhooks: 44 | defaulting: true 45 | validation: true 46 | webhookVersion: v1 47 | version: "3" 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

KasmCloud

3 | 4 | Managing and Running Actors, Providers, and Links in Kubernetes 5 |
6 | 7 | ## :warning:Warning 8 | **This is a contributor-led experimental project and is not recommended to run in production at this time.** 9 | 10 | With each tag, it works fine, but there may be incompatible changes between tags. 11 | 12 | ## Design 13 | 14 | [Combining WasmCloud with Kubernetes](https://docs.google.com/document/d/16p-9czZ6GT_layiabGE6HTyVpbYSALjoyxXhgIfYW0s/edit#heading=h.ymjg4q1g3smk) 15 | 16 |
17 | 18 | ## Quick Start 19 | 1. **Deploy Nats** 20 | ```console 21 | helm repo add nats https://nats-io.github.io/k8s/helm/charts/ 22 | helm repo update 23 | helm upgrade --install kasmcloud-nats nats/nats 24 | ``` 25 | 26 | 2. **Deploy KasmCloud CRDs and Webhook Server** 27 | ```console 28 | kubectl apply -f ./deploy/crds 29 | kubectl apply -f ./deploy/webhook 30 | ``` 31 | 32 | 3. **Deploy KasmCloud Host** 33 | ```console 34 | kubectl apply -f ./deploy/kasmcloud_host_rbac.yaml 35 | 36 | # Deploy Default KasmCloud Host 37 | kubectl apply -f ./deploy/kasmcloud_host_default.yaml 38 | 39 | # [Optional] You can also deploy KasmCloud Host in each Kubernetes node. 40 | kubectl apply -f ./deploy/kasmcloud_host_daemonset.yaml 41 | 42 | # [Optional] You can also deploy as many temporary hosts as you want 43 | # and change the number of temporary hosts by scaling the Deployment 44 | kubectl apply -f ./deploy/kasmcloud_host_deployment.yaml 45 | ``` 46 | 47 | 4. **Deploy Actor, Link and Provider Sample** 48 | ```console 49 | kubectl apply -f ./sample.yaml 50 | 51 | kubectl get kasmcloud 52 | OUTPUT: 53 | NAME DESC PUBLICKEY REPLICAS AVAILABLEREPLICAS CAPS IMAGE 54 | actor.kasmcloud.io/echo-default Echo MBCFOPM6JW2APJLXJD3Z5O4CN7CPYJ2B4FTKLJUR5YR5MITIU7HD3WD5 10 10 ["wasmcloud:httpserver","wasmcloud:builtin:logging"] wasmcloud.azurecr.io/echo:0.3.8 55 | 56 | NAME CONTRACTID LINK ACTORYKEY PROVIDERKEY 57 | link.kasmcloud.io/httpserver-echo wasmcloud:httpserver test MBCFOPM6JW2APJLXJD3Z5O4CN7CPYJ2B4FTKLJUR5YR5MITIU7HD3WD5 VAG3QITQQ2ODAOWB5TTQSDJ53XK3SHBEIFNK4AYJ5RKAX2UNSCAPHA5M 58 | 59 | NAME DESC PUBLICKEY LINK CONTRACTID IMAGE 60 | provider.kasmcloud.io/httpserver-default HTTP Server VAG3QITQQ2ODAOWB5TTQSDJ53XK3SHBEIFNK4AYJ5RKAX2UNSCAPHA5M test wasmcloud:httpserver ghcr.io/iceber/wasmcloud/httpserver:0.17.0-index 61 | ``` 62 | 63 | 4. **curl echo server** 64 | ```console 65 | # other terminal 66 | kubectl port-forward pod/kasmcloud-host-default 8080:8080 67 | 68 | curl 127.0.0.1:8080 69 | {"body":[],"method":"GET","path":"/","query_string":""} 70 | ``` 71 | 72 | ## RoadMap 73 | * Add KasmCloudHost resource 74 | * Add status information for the resource 75 | * Add Kasmcloud Repeater module 76 | * Add rolling updates for Actor 77 | * Add DaemonSet deployment for Actor 78 | * Blue/Green Deployment for Actors and Providers 79 | -------------------------------------------------------------------------------- /api/v1alpha1/actor_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // ActorSpec defines the desired state of Actor 27 | type ActorSpec struct { 28 | // +kubebuilder:validation:MinLength:=1 29 | Host string `json:"host"` 30 | 31 | // +kubebuilder:validation:MinLength:=1 32 | Image string `json:"image"` 33 | 34 | Replicas uint `json:"replicas"` 35 | } 36 | 37 | // ActorStatus defines the observed state of Actor 38 | type ActorStatus struct { 39 | PublicKey string `json:"publicKey"` 40 | DescriptiveName string `json:"descriptiveName,omitempty"` 41 | 42 | Caps []string `json:"caps,omitempty"` 43 | CapabilityProvider []string `json:"capabilityProvider,omitempty"` 44 | CallAlias *string `json:"callAlias,omitempty"` 45 | Claims Claims `json:"claims"` 46 | 47 | Version *string `json:"version,omitempty"` 48 | Reversion *int `json:"reversion,omitempty"` 49 | 50 | Conditions []metav1.Condition `json:"conditions"` 51 | Replicas uint `json:"replicas"` 52 | AvailableReplicas uint `json:"availableReplicas"` 53 | } 54 | 55 | // +kubebuilder:object:root=true 56 | // +kubebuilder:resource:path=actors,scope=Namespaced,categories=kasmcloud 57 | // +kubebuilder:subresource:status 58 | // +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas 59 | // +kubebuilder:printcolumn:name="Desc",type="string",JSONPath=".status.descriptiveName" 60 | // +kubebuilder:printcolumn:name="PublicKey",type="string",JSONPath=".status.publicKey" 61 | // +kubebuilder:printcolumn:name="Replicas",type="integer",JSONPath=".spec.replicas" 62 | // +kubebuilder:printcolumn:name="AvailableReplicas",type="integer",JSONPath=".status.availableReplicas" 63 | // +kubebuilder:printcolumn:name="Caps",type="string",JSONPath=".status.capabilityProvider" 64 | // +kubebuilder:printcolumn:name="Image",type="string",JSONPath=".spec.image" 65 | 66 | // Actor is the Schema for the actors API 67 | type Actor struct { 68 | metav1.TypeMeta `json:",inline"` 69 | metav1.ObjectMeta `json:"metadata,omitempty"` 70 | 71 | Spec ActorSpec `json:"spec,omitempty"` 72 | Status ActorStatus `json:"status,omitempty"` 73 | } 74 | 75 | //+kubebuilder:object:root=true 76 | 77 | // ActorList contains a list of Actor 78 | type ActorList struct { 79 | metav1.TypeMeta `json:",inline"` 80 | metav1.ListMeta `json:"metadata,omitempty"` 81 | Items []Actor `json:"items"` 82 | } 83 | 84 | func init() { 85 | SchemeBuilder.Register(&Actor{}, &ActorList{}) 86 | } 87 | -------------------------------------------------------------------------------- /api/v1alpha1/actor_webhook.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "fmt" 21 | 22 | apierrors "k8s.io/apimachinery/pkg/api/errors" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | "k8s.io/apimachinery/pkg/util/validation/field" 25 | ctrl "sigs.k8s.io/controller-runtime" 26 | logf "sigs.k8s.io/controller-runtime/pkg/log" 27 | "sigs.k8s.io/controller-runtime/pkg/webhook" 28 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 29 | ) 30 | 31 | // log is for logging in this package. 32 | var actorlog = logf.Log.WithName("actor-resource") 33 | 34 | func (r *Actor) SetupWebhookWithManager(mgr ctrl.Manager) error { 35 | return ctrl.NewWebhookManagedBy(mgr). 36 | For(r). 37 | Complete() 38 | } 39 | 40 | // TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 41 | 42 | //+kubebuilder:webhook:path=/mutate-kasmcloud-io-v1alpha1-actor,mutating=true,failurePolicy=fail,sideEffects=None,groups=kasmcloud.io,resources=actors,verbs=create;update,versions=v1alpha1,name=mactor.kb.io,admissionReviewVersions=v1 43 | 44 | var _ webhook.Defaulter = &Actor{} 45 | 46 | // Default implements webhook.Defaulter so a webhook will be registered for the type 47 | func (r *Actor) Default() { 48 | actorlog.Info("default", "name", r.Name) 49 | 50 | if r.Labels == nil { 51 | r.Labels = make(map[string]string) 52 | } 53 | 54 | r.Labels["host"] = r.Spec.Host 55 | 56 | if r.Status.PublicKey != "" { 57 | r.Labels["publickey"] = r.Status.PublicKey 58 | } else { 59 | delete(r.Labels, "publickey") 60 | } 61 | 62 | if r.Status.DescriptiveName != "" { 63 | r.Labels["desc"] = r.Status.DescriptiveName 64 | } else { 65 | delete(r.Labels, "desc") 66 | } 67 | 68 | r.Status.Replicas = r.Spec.Replicas 69 | } 70 | 71 | // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. 72 | //+kubebuilder:webhook:path=/validate-kasmcloud-io-v1alpha1-actor,mutating=false,failurePolicy=fail,sideEffects=None,groups=kasmcloud.io,resources=actors,verbs=create;update,versions=v1alpha1,name=vactor.kb.io,admissionReviewVersions=v1 73 | 74 | var _ webhook.Validator = &Actor{} 75 | 76 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type 77 | func (r *Actor) ValidateCreate() (admission.Warnings, error) { 78 | actorlog.Info("validate create", "name", r.Name) 79 | return nil, nil 80 | } 81 | 82 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type 83 | func (r *Actor) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { 84 | actorlog.Info("validate update", "name", r.Name) 85 | 86 | var allErrs field.ErrorList 87 | 88 | oldObj, ok := old.(*Actor) 89 | if !ok { 90 | return nil, fmt.Errorf("expected a *Actor, but got a %T", old) 91 | } 92 | 93 | if r.Spec.Host != oldObj.Spec.Host { 94 | allErrs = append(allErrs, field.Invalid( 95 | field.NewPath("spec", "host"), 96 | r.Spec.Host, 97 | "spec.host could not be changed", 98 | )) 99 | } 100 | 101 | if len(allErrs) != 0 { 102 | return nil, apierrors.NewInvalid(GroupVersion.WithKind("Actor").GroupKind(), r.Name, allErrs) 103 | } 104 | return nil, nil 105 | } 106 | 107 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type 108 | func (r *Actor) ValidateDelete() (admission.Warnings, error) { 109 | actorlog.Info("validate delete", "name", r.Name) 110 | 111 | return nil, nil 112 | } 113 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=kasmcloud.io 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "kasmcloud.io", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/v1alpha1/kasmcloudhost_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // KasmCloudHostSpec defines the desired state of KasmCloudHost 27 | type KasmCloudHostSpec struct { 28 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 29 | // Important: Run "make" to regenerate code after modifying this file 30 | 31 | ClusterIssuers []string `json:"clusterIssuers,omitempty"` 32 | } 33 | 34 | // KasmCloudHostStatus defines the observed state of KasmCloudHost 35 | type KasmCloudHostStatus struct { 36 | KubeNodeName string `json:"kubeNodeName,omitempty"` 37 | Instance string `json:"instance,omitempty"` 38 | PreInstance string `json:"preInstance,omitempty"` 39 | 40 | PublicKey string `json:"publicKey"` 41 | ClusterPublicKey string `json:"clusterPublicKey"` 42 | ClusterIssuers []string `json:"clusterIssuers"` 43 | 44 | Providers KasmCloudHostProviderStatus `json:"providers"` 45 | Actors KasmCloudHostActorStatus `json:"actors"` 46 | } 47 | 48 | type KasmCloudHostProviderStatus struct { 49 | Count uint64 `json:"count"` 50 | } 51 | 52 | type KasmCloudHostActorStatus struct { 53 | Count uint64 `json:"count"` 54 | InstanceCount uint64 `json:"instanceCount"` 55 | } 56 | 57 | //+kubebuilder:object:root=true 58 | //+kubebuilder:subresource:status 59 | 60 | // KasmCloudHost is the Schema for the kasmcloudhosts API 61 | type KasmCloudHost struct { 62 | metav1.TypeMeta `json:",inline"` 63 | metav1.ObjectMeta `json:"metadata,omitempty"` 64 | 65 | Spec KasmCloudHostSpec `json:"spec,omitempty"` 66 | Status KasmCloudHostStatus `json:"status,omitempty"` 67 | } 68 | 69 | //+kubebuilder:object:root=true 70 | 71 | // KasmCloudHostList contains a list of KasmCloudHost 72 | type KasmCloudHostList struct { 73 | metav1.TypeMeta `json:",inline"` 74 | metav1.ListMeta `json:"metadata,omitempty"` 75 | Items []KasmCloudHost `json:"items"` 76 | } 77 | 78 | func init() { 79 | SchemeBuilder.Register(&KasmCloudHost{}, &KasmCloudHostList{}) 80 | } 81 | -------------------------------------------------------------------------------- /api/v1alpha1/link_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // LinkSpec defines the desired state of Link 27 | type LinkSpec struct { 28 | LinkName string `json:"linkName,omitempty"` 29 | 30 | Provider Source `json:"provider"` 31 | Actor Source `json:"actor"` 32 | 33 | // +kubebuilder:validation:MinLength:=1 34 | ContractId string `json:"contractId"` 35 | 36 | Values map[string]string `json:"values,omitempty"` 37 | } 38 | 39 | // LinkStatus defines the observed state of Link 40 | type LinkStatus struct { 41 | ProviderKey string `json:"providerKey"` 42 | ActorKey string `json:"actorKey"` 43 | LinkName string `json:"linkName"` 44 | 45 | Conditions []metav1.Condition `json:"conditions"` 46 | } 47 | 48 | type Source struct { 49 | Key string `json:"key,omitempty"` 50 | Name string `json:"name,omitempty"` 51 | } 52 | 53 | // +kubebuilder:object:root=true 54 | // +kubebuilder:resource:path=links,scope=Namespaced,categories=kasmcloud 55 | // +kubebuilder:subresource:status 56 | // +kubebuilder:printcolumn:name="ContractId",type="string",JSONPath=".spec.contractId" 57 | // +kubebuilder:printcolumn:name="Link",type="string",JSONPath=".status.linkName" 58 | // +kubebuilder:printcolumn:name="ActoryKey",type="string",JSONPath=".status.actorKey" 59 | // +kubebuilder:printcolumn:name="ProviderKey",type="string",JSONPath=".status.providerKey" 60 | 61 | // Link is the Schema for the links API 62 | type Link struct { 63 | metav1.TypeMeta `json:",inline"` 64 | metav1.ObjectMeta `json:"metadata,omitempty"` 65 | 66 | Spec LinkSpec `json:"spec,omitempty"` 67 | Status LinkStatus `json:"status,omitempty"` 68 | } 69 | 70 | //+kubebuilder:object:root=true 71 | 72 | // LinkList contains a list of Link 73 | type LinkList struct { 74 | metav1.TypeMeta `json:",inline"` 75 | metav1.ListMeta `json:"metadata,omitempty"` 76 | Items []Link `json:"items"` 77 | } 78 | 79 | func init() { 80 | SchemeBuilder.Register(&Link{}, &LinkList{}) 81 | } 82 | -------------------------------------------------------------------------------- /api/v1alpha1/link_webhook.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "fmt" 21 | "reflect" 22 | "strings" 23 | 24 | apierrors "k8s.io/apimachinery/pkg/api/errors" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "k8s.io/apimachinery/pkg/util/validation/field" 27 | ctrl "sigs.k8s.io/controller-runtime" 28 | logf "sigs.k8s.io/controller-runtime/pkg/log" 29 | "sigs.k8s.io/controller-runtime/pkg/webhook" 30 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 31 | ) 32 | 33 | // log is for logging in this package. 34 | var linklog = logf.Log.WithName("link-resource") 35 | 36 | func (r *Link) SetupWebhookWithManager(mgr ctrl.Manager) error { 37 | return ctrl.NewWebhookManagedBy(mgr). 38 | For(r). 39 | Complete() 40 | } 41 | 42 | // TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 43 | 44 | //+kubebuilder:webhook:path=/mutate-kasmcloud-io-v1alpha1-link,mutating=true,failurePolicy=fail,sideEffects=None,groups=kasmcloud.io,resources=links,verbs=create;update,versions=v1alpha1,name=mlink.kb.io,admissionReviewVersions=v1 45 | 46 | var _ webhook.Defaulter = &Link{} 47 | 48 | // Default implements webhook.Defaulter so a webhook will be registered for the type 49 | func (r *Link) Default() { 50 | linklog.Info("default", "name", r.Name) 51 | 52 | if r.Labels == nil { 53 | r.Labels = make(map[string]string) 54 | } 55 | 56 | // `contractId: wasmcloud:httpserver` -> `contract=wasmcloud.httpserver` 57 | r.Labels["contract"] = strings.ReplaceAll(r.Spec.ContractId, ":", ".") 58 | 59 | if r.Status.ProviderKey != "" { 60 | r.Labels["providerkey"] = r.Status.ProviderKey 61 | } else { 62 | delete(r.Labels, "providerkey") 63 | } 64 | 65 | if r.Status.LinkName != "" { 66 | r.Labels["link"] = r.Status.LinkName 67 | } else { 68 | delete(r.Labels, "link") 69 | } 70 | 71 | if r.Status.ActorKey != "" { 72 | r.Labels["actorkey"] = r.Status.ActorKey 73 | } else { 74 | delete(r.Labels, "actorkey") 75 | } 76 | } 77 | 78 | // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. 79 | //+kubebuilder:webhook:path=/validate-kasmcloud-io-v1alpha1-link,mutating=false,failurePolicy=fail,sideEffects=None,groups=kasmcloud.io,resources=links,verbs=create;update,versions=v1alpha1,name=vlink.kb.io,admissionReviewVersions=v1 80 | 81 | var _ webhook.Validator = &Link{} 82 | 83 | func (r *Link) validate() (allErrs field.ErrorList) { 84 | if r.Spec.Actor.Key == "" && r.Spec.Actor.Name == "" { 85 | allErrs = append(allErrs, field.Invalid( 86 | field.NewPath("spec", "actor"), 87 | "", 88 | "required .spec.actor.name or .spec.actor.key", 89 | )) 90 | } 91 | 92 | if r.Spec.Provider.Key == "" && r.Spec.Provider.Name == "" { 93 | allErrs = append(allErrs, field.Invalid( 94 | field.NewPath("spec", "provider"), 95 | "", 96 | "required .spec.provider.name or .spec.provider.key", 97 | )) 98 | } else if r.Spec.Provider.Name == "" && r.Spec.LinkName == "" { 99 | allErrs = append(allErrs, field.Invalid( 100 | field.NewPath("spec", "linkName"), 101 | "", 102 | "must set spec.linkName, if only set spec.provider.key", 103 | )) 104 | } 105 | return 106 | } 107 | 108 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type 109 | func (r *Link) ValidateCreate() (admission.Warnings, error) { 110 | linklog.Info("validate create", "name", r.Name) 111 | 112 | if allErrs := r.validate(); len(allErrs) != 0 { 113 | return nil, apierrors.NewInvalid(GroupVersion.WithKind("Link").GroupKind(), r.Name, allErrs) 114 | } 115 | return nil, nil 116 | } 117 | 118 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type 119 | func (r *Link) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { 120 | linklog.Info("validate update", "name", r.Name) 121 | 122 | oldObj, ok := old.(*Link) 123 | if !ok { 124 | return nil, apierrors.NewBadRequest(fmt.Sprintf("expected *Link but got a %T", old)) 125 | } 126 | 127 | if !reflect.DeepEqual(r.Spec, oldObj.Spec) { 128 | return nil, apierrors.NewInvalid(GroupVersion.WithKind("Link").GroupKind(), r.Name, field.ErrorList{ 129 | field.Invalid(field.NewPath("spec"), "", "spec could not be changed"), 130 | }) 131 | } 132 | 133 | if allErrs := r.validate(); len(allErrs) != 0 { 134 | return nil, apierrors.NewInvalid(GroupVersion.WithKind("Link").GroupKind(), r.Name, allErrs) 135 | } 136 | return nil, nil 137 | } 138 | 139 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type 140 | func (r *Link) ValidateDelete() (admission.Warnings, error) { 141 | linklog.Info("validate delete", "name", r.Name) 142 | 143 | // TODO(user): fill in your validation logic upon object deletion. 144 | return nil, nil 145 | } 146 | -------------------------------------------------------------------------------- /api/v1alpha1/provider_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // ProviderSpec defines the desired state of Provider 27 | type ProviderSpec struct { 28 | // +kubebuilder:validation:MinLength:=1 29 | Host string `json:"host"` 30 | 31 | // +kubebuilder:validation:MinLength:=1 32 | Image string `json:"image"` 33 | 34 | // +kubebuilder:validation:MinLength:=1 35 | Link string `json:"link"` 36 | } 37 | 38 | // ProviderStatus defines the observed state of Provider 39 | type ProviderStatus struct { 40 | PublicKey string `json:"publicKey"` 41 | ContractId string `json:"contractId"` 42 | DescriptiveName string `json:"descriptiveName,omitempty"` 43 | 44 | Claims Claims `json:"claims"` 45 | ArchitectureTargets []string `json:"architectureTargets"` 46 | 47 | Vendor string `json:"vendor"` 48 | Version *string `json:"version,omitempty"` 49 | Reversion *int `json:"reversion,omitempty"` 50 | 51 | Conditions []metav1.Condition `json:"conditions"` 52 | InstanceId string `json:"instanceId"` 53 | } 54 | 55 | // +kubebuilder:object:root=true 56 | // +kubebuilder:resource:path=providers,scope=Namespaced,categories=kasmcloud 57 | // +kubebuilder:subresource:status 58 | // +kubebuilder:printcolumn:name="Desc",type="string",JSONPath=".status.descriptiveName" 59 | // +kubebuilder:printcolumn:name="PublicKey",type="string",JSONPath=".status.publicKey" 60 | // +kubebuilder:printcolumn:name="Link",type="string",JSONPath=".spec.link" 61 | // +kubebuilder:printcolumn:name="ContractId",type="string",JSONPath=".status.contractId" 62 | // +kubebuilder:printcolumn:name="Image",type="string",JSONPath=".spec.image" 63 | 64 | // Provider is the Schema for the providers API 65 | type Provider struct { 66 | metav1.TypeMeta `json:",inline"` 67 | metav1.ObjectMeta `json:"metadata,omitempty"` 68 | 69 | Spec ProviderSpec `json:"spec,omitempty"` 70 | Status ProviderStatus `json:"status,omitempty"` 71 | } 72 | 73 | //+kubebuilder:object:root=true 74 | 75 | // ProviderList contains a list of Provider 76 | type ProviderList struct { 77 | metav1.TypeMeta `json:",inline"` 78 | metav1.ListMeta `json:"metadata,omitempty"` 79 | Items []Provider `json:"items"` 80 | } 81 | 82 | func init() { 83 | SchemeBuilder.Register(&Provider{}, &ProviderList{}) 84 | } 85 | -------------------------------------------------------------------------------- /api/v1alpha1/provider_webhook.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "fmt" 21 | 22 | apierrors "k8s.io/apimachinery/pkg/api/errors" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | "k8s.io/apimachinery/pkg/util/validation/field" 25 | ctrl "sigs.k8s.io/controller-runtime" 26 | logf "sigs.k8s.io/controller-runtime/pkg/log" 27 | "sigs.k8s.io/controller-runtime/pkg/webhook" 28 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 29 | ) 30 | 31 | // log is for logging in this package. 32 | var providerlog = logf.Log.WithName("provider-resource") 33 | 34 | func (r *Provider) SetupWebhookWithManager(mgr ctrl.Manager) error { 35 | return ctrl.NewWebhookManagedBy(mgr). 36 | For(r). 37 | Complete() 38 | } 39 | 40 | // TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 41 | 42 | //+kubebuilder:webhook:path=/mutate-kasmcloud-io-v1alpha1-provider,mutating=true,failurePolicy=fail,sideEffects=None,groups=kasmcloud.io,resources=providers,verbs=create;update,versions=v1alpha1,name=mprovider.kb.io,admissionReviewVersions=v1 43 | 44 | var _ webhook.Defaulter = &Provider{} 45 | 46 | // Default implements webhook.Defaulter so a webhook will be registered for the type 47 | func (r *Provider) Default() { 48 | providerlog.Info("default", "name", r.Name) 49 | 50 | if r.Labels == nil { 51 | r.Labels = make(map[string]string) 52 | } 53 | 54 | r.Labels["host"] = r.Spec.Host 55 | r.Labels["link"] = r.Spec.Link 56 | 57 | if r.Status.PublicKey != "" { 58 | r.Labels["publickey"] = r.Status.PublicKey 59 | } else { 60 | delete(r.Labels, "publickey") 61 | } 62 | 63 | if r.Status.DescriptiveName != "" { 64 | r.Labels["desc"] = r.Status.DescriptiveName 65 | } else { 66 | delete(r.Labels, "desc") 67 | } 68 | } 69 | 70 | // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. 71 | //+kubebuilder:webhook:path=/validate-kasmcloud-io-v1alpha1-provider,mutating=false,failurePolicy=fail,sideEffects=None,groups=kasmcloud.io,resources=providers,verbs=create;update,versions=v1alpha1,name=vprovider.kb.io,admissionReviewVersions=v1 72 | 73 | var _ webhook.Validator = &Provider{} 74 | 75 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type 76 | func (r *Provider) ValidateCreate() (admission.Warnings, error) { 77 | providerlog.Info("validate create", "name", r.Name) 78 | 79 | return nil, nil 80 | } 81 | 82 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type 83 | func (r *Provider) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { 84 | providerlog.Info("validate update", "name", r.Name) 85 | 86 | oldObj, ok := old.(*Provider) 87 | if !ok { 88 | return nil, apierrors.NewBadRequest(fmt.Sprintf("expected *Provider but got a %T", old)) 89 | } 90 | 91 | var allErrs field.ErrorList 92 | 93 | if r.Spec.Host != oldObj.Spec.Host { 94 | allErrs = append(allErrs, field.Invalid( 95 | field.NewPath("spec", "host"), 96 | r.Spec.Host, 97 | "spec.host could not be changed", 98 | )) 99 | } 100 | 101 | if r.Spec.Link != oldObj.Spec.Link { 102 | allErrs = append(allErrs, field.Invalid( 103 | field.NewPath("spec", "link"), 104 | r.Spec.Link, 105 | "spec.link could not be changed", 106 | )) 107 | } 108 | 109 | if len(allErrs) != 0 { 110 | return nil, apierrors.NewInvalid(GroupVersion.WithKind("Provider").GroupKind(), r.Name, allErrs) 111 | } 112 | return nil, nil 113 | } 114 | 115 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type 116 | func (r *Provider) ValidateDelete() (admission.Warnings, error) { 117 | providerlog.Info("validate delete", "name", r.Name) 118 | 119 | // TODO(user): fill in your validation logic upon object deletion. 120 | return nil, nil 121 | } 122 | -------------------------------------------------------------------------------- /api/v1alpha1/types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 4 | 5 | type Claims struct { 6 | Issuer string `json:"issuer"` 7 | Subject string `json:"subject"` 8 | IssuedAt metav1.Time `json:"issuedAt"` 9 | NotBefore *metav1.Time `json:"notBefore,omitempty"` 10 | Expires *metav1.Time `json:"expires,omitempty"` 11 | } 12 | -------------------------------------------------------------------------------- /api/v1alpha1/webhook_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "context" 21 | "crypto/tls" 22 | "fmt" 23 | "net" 24 | "path/filepath" 25 | "testing" 26 | "time" 27 | 28 | . "github.com/onsi/ginkgo/v2" 29 | . "github.com/onsi/gomega" 30 | 31 | admissionv1 "k8s.io/api/admission/v1" 32 | //+kubebuilder:scaffold:imports 33 | "k8s.io/apimachinery/pkg/runtime" 34 | "k8s.io/client-go/rest" 35 | ctrl "sigs.k8s.io/controller-runtime" 36 | "sigs.k8s.io/controller-runtime/pkg/client" 37 | "sigs.k8s.io/controller-runtime/pkg/envtest" 38 | logf "sigs.k8s.io/controller-runtime/pkg/log" 39 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 40 | ) 41 | 42 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 43 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 44 | 45 | var cfg *rest.Config 46 | var k8sClient client.Client 47 | var testEnv *envtest.Environment 48 | var ctx context.Context 49 | var cancel context.CancelFunc 50 | 51 | func TestAPIs(t *testing.T) { 52 | RegisterFailHandler(Fail) 53 | 54 | RunSpecs(t, "Webhook Suite") 55 | } 56 | 57 | var _ = BeforeSuite(func() { 58 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 59 | 60 | ctx, cancel = context.WithCancel(context.TODO()) 61 | 62 | By("bootstrapping test environment") 63 | testEnv = &envtest.Environment{ 64 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, 65 | ErrorIfCRDPathMissing: false, 66 | WebhookInstallOptions: envtest.WebhookInstallOptions{ 67 | Paths: []string{filepath.Join("..", "..", "config", "webhook")}, 68 | }, 69 | } 70 | 71 | var err error 72 | // cfg is defined in this file globally. 73 | cfg, err = testEnv.Start() 74 | Expect(err).NotTo(HaveOccurred()) 75 | Expect(cfg).NotTo(BeNil()) 76 | 77 | scheme := runtime.NewScheme() 78 | err = AddToScheme(scheme) 79 | Expect(err).NotTo(HaveOccurred()) 80 | 81 | err = admissionv1.AddToScheme(scheme) 82 | Expect(err).NotTo(HaveOccurred()) 83 | 84 | //+kubebuilder:scaffold:scheme 85 | 86 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) 87 | Expect(err).NotTo(HaveOccurred()) 88 | Expect(k8sClient).NotTo(BeNil()) 89 | 90 | // start webhook server using Manager 91 | webhookInstallOptions := &testEnv.WebhookInstallOptions 92 | mgr, err := ctrl.NewManager(cfg, ctrl.Options{ 93 | Scheme: scheme, 94 | Host: webhookInstallOptions.LocalServingHost, 95 | Port: webhookInstallOptions.LocalServingPort, 96 | CertDir: webhookInstallOptions.LocalServingCertDir, 97 | LeaderElection: false, 98 | MetricsBindAddress: "0", 99 | }) 100 | Expect(err).NotTo(HaveOccurred()) 101 | 102 | err = (&Actor{}).SetupWebhookWithManager(mgr) 103 | Expect(err).NotTo(HaveOccurred()) 104 | 105 | err = (&Provider{}).SetupWebhookWithManager(mgr) 106 | Expect(err).NotTo(HaveOccurred()) 107 | 108 | err = (&Link{}).SetupWebhookWithManager(mgr) 109 | Expect(err).NotTo(HaveOccurred()) 110 | 111 | //+kubebuilder:scaffold:webhook 112 | 113 | go func() { 114 | defer GinkgoRecover() 115 | err = mgr.Start(ctx) 116 | Expect(err).NotTo(HaveOccurred()) 117 | }() 118 | 119 | // wait for the webhook server to get ready 120 | dialer := &net.Dialer{Timeout: time.Second} 121 | addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) 122 | Eventually(func() error { 123 | conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) 124 | if err != nil { 125 | return err 126 | } 127 | conn.Close() 128 | return nil 129 | }).Should(Succeed()) 130 | 131 | }) 132 | 133 | var _ = AfterSuite(func() { 134 | cancel() 135 | By("tearing down the test environment") 136 | err := testEnv.Stop() 137 | Expect(err).NotTo(HaveOccurred()) 138 | }) 139 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright 2023. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by controller-gen. DO NOT EDIT. 21 | 22 | package v1alpha1 23 | 24 | import ( 25 | "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | ) 28 | 29 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 30 | func (in *Actor) DeepCopyInto(out *Actor) { 31 | *out = *in 32 | out.TypeMeta = in.TypeMeta 33 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 34 | out.Spec = in.Spec 35 | in.Status.DeepCopyInto(&out.Status) 36 | } 37 | 38 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Actor. 39 | func (in *Actor) DeepCopy() *Actor { 40 | if in == nil { 41 | return nil 42 | } 43 | out := new(Actor) 44 | in.DeepCopyInto(out) 45 | return out 46 | } 47 | 48 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 49 | func (in *Actor) DeepCopyObject() runtime.Object { 50 | if c := in.DeepCopy(); c != nil { 51 | return c 52 | } 53 | return nil 54 | } 55 | 56 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 57 | func (in *ActorList) DeepCopyInto(out *ActorList) { 58 | *out = *in 59 | out.TypeMeta = in.TypeMeta 60 | in.ListMeta.DeepCopyInto(&out.ListMeta) 61 | if in.Items != nil { 62 | in, out := &in.Items, &out.Items 63 | *out = make([]Actor, len(*in)) 64 | for i := range *in { 65 | (*in)[i].DeepCopyInto(&(*out)[i]) 66 | } 67 | } 68 | } 69 | 70 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ActorList. 71 | func (in *ActorList) DeepCopy() *ActorList { 72 | if in == nil { 73 | return nil 74 | } 75 | out := new(ActorList) 76 | in.DeepCopyInto(out) 77 | return out 78 | } 79 | 80 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 81 | func (in *ActorList) DeepCopyObject() runtime.Object { 82 | if c := in.DeepCopy(); c != nil { 83 | return c 84 | } 85 | return nil 86 | } 87 | 88 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 89 | func (in *ActorSpec) DeepCopyInto(out *ActorSpec) { 90 | *out = *in 91 | } 92 | 93 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ActorSpec. 94 | func (in *ActorSpec) DeepCopy() *ActorSpec { 95 | if in == nil { 96 | return nil 97 | } 98 | out := new(ActorSpec) 99 | in.DeepCopyInto(out) 100 | return out 101 | } 102 | 103 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 104 | func (in *ActorStatus) DeepCopyInto(out *ActorStatus) { 105 | *out = *in 106 | if in.Caps != nil { 107 | in, out := &in.Caps, &out.Caps 108 | *out = make([]string, len(*in)) 109 | copy(*out, *in) 110 | } 111 | if in.CapabilityProvider != nil { 112 | in, out := &in.CapabilityProvider, &out.CapabilityProvider 113 | *out = make([]string, len(*in)) 114 | copy(*out, *in) 115 | } 116 | if in.CallAlias != nil { 117 | in, out := &in.CallAlias, &out.CallAlias 118 | *out = new(string) 119 | **out = **in 120 | } 121 | in.Claims.DeepCopyInto(&out.Claims) 122 | if in.Version != nil { 123 | in, out := &in.Version, &out.Version 124 | *out = new(string) 125 | **out = **in 126 | } 127 | if in.Reversion != nil { 128 | in, out := &in.Reversion, &out.Reversion 129 | *out = new(int) 130 | **out = **in 131 | } 132 | if in.Conditions != nil { 133 | in, out := &in.Conditions, &out.Conditions 134 | *out = make([]v1.Condition, len(*in)) 135 | for i := range *in { 136 | (*in)[i].DeepCopyInto(&(*out)[i]) 137 | } 138 | } 139 | } 140 | 141 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ActorStatus. 142 | func (in *ActorStatus) DeepCopy() *ActorStatus { 143 | if in == nil { 144 | return nil 145 | } 146 | out := new(ActorStatus) 147 | in.DeepCopyInto(out) 148 | return out 149 | } 150 | 151 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 152 | func (in *Claims) DeepCopyInto(out *Claims) { 153 | *out = *in 154 | in.IssuedAt.DeepCopyInto(&out.IssuedAt) 155 | if in.NotBefore != nil { 156 | in, out := &in.NotBefore, &out.NotBefore 157 | *out = (*in).DeepCopy() 158 | } 159 | if in.Expires != nil { 160 | in, out := &in.Expires, &out.Expires 161 | *out = (*in).DeepCopy() 162 | } 163 | } 164 | 165 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Claims. 166 | func (in *Claims) DeepCopy() *Claims { 167 | if in == nil { 168 | return nil 169 | } 170 | out := new(Claims) 171 | in.DeepCopyInto(out) 172 | return out 173 | } 174 | 175 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 176 | func (in *KasmCloudHost) DeepCopyInto(out *KasmCloudHost) { 177 | *out = *in 178 | out.TypeMeta = in.TypeMeta 179 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 180 | in.Spec.DeepCopyInto(&out.Spec) 181 | out.Status = in.Status 182 | } 183 | 184 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KasmCloudHost. 185 | func (in *KasmCloudHost) DeepCopy() *KasmCloudHost { 186 | if in == nil { 187 | return nil 188 | } 189 | out := new(KasmCloudHost) 190 | in.DeepCopyInto(out) 191 | return out 192 | } 193 | 194 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 195 | func (in *KasmCloudHost) DeepCopyObject() runtime.Object { 196 | if c := in.DeepCopy(); c != nil { 197 | return c 198 | } 199 | return nil 200 | } 201 | 202 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 203 | func (in *KasmCloudHostList) DeepCopyInto(out *KasmCloudHostList) { 204 | *out = *in 205 | out.TypeMeta = in.TypeMeta 206 | in.ListMeta.DeepCopyInto(&out.ListMeta) 207 | if in.Items != nil { 208 | in, out := &in.Items, &out.Items 209 | *out = make([]KasmCloudHost, len(*in)) 210 | for i := range *in { 211 | (*in)[i].DeepCopyInto(&(*out)[i]) 212 | } 213 | } 214 | } 215 | 216 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KasmCloudHostList. 217 | func (in *KasmCloudHostList) DeepCopy() *KasmCloudHostList { 218 | if in == nil { 219 | return nil 220 | } 221 | out := new(KasmCloudHostList) 222 | in.DeepCopyInto(out) 223 | return out 224 | } 225 | 226 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 227 | func (in *KasmCloudHostList) DeepCopyObject() runtime.Object { 228 | if c := in.DeepCopy(); c != nil { 229 | return c 230 | } 231 | return nil 232 | } 233 | 234 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 235 | func (in *KasmCloudHostSpec) DeepCopyInto(out *KasmCloudHostSpec) { 236 | *out = *in 237 | if in.ClusterIssuers != nil { 238 | in, out := &in.ClusterIssuers, &out.ClusterIssuers 239 | *out = make([]string, len(*in)) 240 | copy(*out, *in) 241 | } 242 | } 243 | 244 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KasmCloudHostSpec. 245 | func (in *KasmCloudHostSpec) DeepCopy() *KasmCloudHostSpec { 246 | if in == nil { 247 | return nil 248 | } 249 | out := new(KasmCloudHostSpec) 250 | in.DeepCopyInto(out) 251 | return out 252 | } 253 | 254 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 255 | func (in *KasmCloudHostStatus) DeepCopyInto(out *KasmCloudHostStatus) { 256 | *out = *in 257 | } 258 | 259 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KasmCloudHostStatus. 260 | func (in *KasmCloudHostStatus) DeepCopy() *KasmCloudHostStatus { 261 | if in == nil { 262 | return nil 263 | } 264 | out := new(KasmCloudHostStatus) 265 | in.DeepCopyInto(out) 266 | return out 267 | } 268 | 269 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 270 | func (in *Link) DeepCopyInto(out *Link) { 271 | *out = *in 272 | out.TypeMeta = in.TypeMeta 273 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 274 | in.Spec.DeepCopyInto(&out.Spec) 275 | in.Status.DeepCopyInto(&out.Status) 276 | } 277 | 278 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Link. 279 | func (in *Link) DeepCopy() *Link { 280 | if in == nil { 281 | return nil 282 | } 283 | out := new(Link) 284 | in.DeepCopyInto(out) 285 | return out 286 | } 287 | 288 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 289 | func (in *Link) DeepCopyObject() runtime.Object { 290 | if c := in.DeepCopy(); c != nil { 291 | return c 292 | } 293 | return nil 294 | } 295 | 296 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 297 | func (in *LinkList) DeepCopyInto(out *LinkList) { 298 | *out = *in 299 | out.TypeMeta = in.TypeMeta 300 | in.ListMeta.DeepCopyInto(&out.ListMeta) 301 | if in.Items != nil { 302 | in, out := &in.Items, &out.Items 303 | *out = make([]Link, len(*in)) 304 | for i := range *in { 305 | (*in)[i].DeepCopyInto(&(*out)[i]) 306 | } 307 | } 308 | } 309 | 310 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkList. 311 | func (in *LinkList) DeepCopy() *LinkList { 312 | if in == nil { 313 | return nil 314 | } 315 | out := new(LinkList) 316 | in.DeepCopyInto(out) 317 | return out 318 | } 319 | 320 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 321 | func (in *LinkList) DeepCopyObject() runtime.Object { 322 | if c := in.DeepCopy(); c != nil { 323 | return c 324 | } 325 | return nil 326 | } 327 | 328 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 329 | func (in *LinkSpec) DeepCopyInto(out *LinkSpec) { 330 | *out = *in 331 | out.Provider = in.Provider 332 | out.Actor = in.Actor 333 | if in.Values != nil { 334 | in, out := &in.Values, &out.Values 335 | *out = make(map[string]string, len(*in)) 336 | for key, val := range *in { 337 | (*out)[key] = val 338 | } 339 | } 340 | } 341 | 342 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkSpec. 343 | func (in *LinkSpec) DeepCopy() *LinkSpec { 344 | if in == nil { 345 | return nil 346 | } 347 | out := new(LinkSpec) 348 | in.DeepCopyInto(out) 349 | return out 350 | } 351 | 352 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 353 | func (in *LinkStatus) DeepCopyInto(out *LinkStatus) { 354 | *out = *in 355 | if in.Conditions != nil { 356 | in, out := &in.Conditions, &out.Conditions 357 | *out = make([]v1.Condition, len(*in)) 358 | for i := range *in { 359 | (*in)[i].DeepCopyInto(&(*out)[i]) 360 | } 361 | } 362 | } 363 | 364 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkStatus. 365 | func (in *LinkStatus) DeepCopy() *LinkStatus { 366 | if in == nil { 367 | return nil 368 | } 369 | out := new(LinkStatus) 370 | in.DeepCopyInto(out) 371 | return out 372 | } 373 | 374 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 375 | func (in *Provider) DeepCopyInto(out *Provider) { 376 | *out = *in 377 | out.TypeMeta = in.TypeMeta 378 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 379 | out.Spec = in.Spec 380 | in.Status.DeepCopyInto(&out.Status) 381 | } 382 | 383 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Provider. 384 | func (in *Provider) DeepCopy() *Provider { 385 | if in == nil { 386 | return nil 387 | } 388 | out := new(Provider) 389 | in.DeepCopyInto(out) 390 | return out 391 | } 392 | 393 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 394 | func (in *Provider) DeepCopyObject() runtime.Object { 395 | if c := in.DeepCopy(); c != nil { 396 | return c 397 | } 398 | return nil 399 | } 400 | 401 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 402 | func (in *ProviderList) DeepCopyInto(out *ProviderList) { 403 | *out = *in 404 | out.TypeMeta = in.TypeMeta 405 | in.ListMeta.DeepCopyInto(&out.ListMeta) 406 | if in.Items != nil { 407 | in, out := &in.Items, &out.Items 408 | *out = make([]Provider, len(*in)) 409 | for i := range *in { 410 | (*in)[i].DeepCopyInto(&(*out)[i]) 411 | } 412 | } 413 | } 414 | 415 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderList. 416 | func (in *ProviderList) DeepCopy() *ProviderList { 417 | if in == nil { 418 | return nil 419 | } 420 | out := new(ProviderList) 421 | in.DeepCopyInto(out) 422 | return out 423 | } 424 | 425 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 426 | func (in *ProviderList) DeepCopyObject() runtime.Object { 427 | if c := in.DeepCopy(); c != nil { 428 | return c 429 | } 430 | return nil 431 | } 432 | 433 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 434 | func (in *ProviderSpec) DeepCopyInto(out *ProviderSpec) { 435 | *out = *in 436 | } 437 | 438 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderSpec. 439 | func (in *ProviderSpec) DeepCopy() *ProviderSpec { 440 | if in == nil { 441 | return nil 442 | } 443 | out := new(ProviderSpec) 444 | in.DeepCopyInto(out) 445 | return out 446 | } 447 | 448 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 449 | func (in *ProviderStatus) DeepCopyInto(out *ProviderStatus) { 450 | *out = *in 451 | in.Claims.DeepCopyInto(&out.Claims) 452 | if in.ArchitectureTargets != nil { 453 | in, out := &in.ArchitectureTargets, &out.ArchitectureTargets 454 | *out = make([]string, len(*in)) 455 | copy(*out, *in) 456 | } 457 | if in.Version != nil { 458 | in, out := &in.Version, &out.Version 459 | *out = new(string) 460 | **out = **in 461 | } 462 | if in.Reversion != nil { 463 | in, out := &in.Reversion, &out.Reversion 464 | *out = new(int) 465 | **out = **in 466 | } 467 | if in.Conditions != nil { 468 | in, out := &in.Conditions, &out.Conditions 469 | *out = make([]v1.Condition, len(*in)) 470 | for i := range *in { 471 | (*in)[i].DeepCopyInto(&(*out)[i]) 472 | } 473 | } 474 | } 475 | 476 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderStatus. 477 | func (in *ProviderStatus) DeepCopy() *ProviderStatus { 478 | if in == nil { 479 | return nil 480 | } 481 | out := new(ProviderStatus) 482 | in.DeepCopyInto(out) 483 | return out 484 | } 485 | 486 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 487 | func (in *Source) DeepCopyInto(out *Source) { 488 | *out = *in 489 | } 490 | 491 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Source. 492 | func (in *Source) DeepCopy() *Source { 493 | if in == nil { 494 | return nil 495 | } 496 | out := new(Source) 497 | in.DeepCopyInto(out) 498 | return out 499 | } 500 | -------------------------------------------------------------------------------- /arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasmCloud/kasmcloud/f53677be9d94b3995b93eb17dcdab1536ed89747/arch.png -------------------------------------------------------------------------------- /cmd/webhook/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | 23 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 24 | // to ensure that exec-entrypoint and run can make use of them. 25 | _ "k8s.io/client-go/plugin/pkg/client/auth" 26 | 27 | "k8s.io/apimachinery/pkg/runtime" 28 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 29 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 30 | ctrl "sigs.k8s.io/controller-runtime" 31 | "sigs.k8s.io/controller-runtime/pkg/healthz" 32 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 33 | "sigs.k8s.io/controller-runtime/pkg/webhook" 34 | 35 | kasmcloudiov1alpha1 "github.com/wasmcloud/kasmcloud/api/v1alpha1" 36 | //+kubebuilder:scaffold:imports 37 | ) 38 | 39 | var ( 40 | scheme = runtime.NewScheme() 41 | setupLog = ctrl.Log.WithName("setup") 42 | ) 43 | 44 | func init() { 45 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 46 | 47 | utilruntime.Must(kasmcloudiov1alpha1.AddToScheme(scheme)) 48 | //+kubebuilder:scaffold:scheme 49 | } 50 | 51 | func main() { 52 | var metricsAddr string 53 | var probeAddr string 54 | var options webhook.Options 55 | 56 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 57 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 58 | flag.IntVar(&options.Port, "port", webhook.DefaultPort, "The webhook Server port") 59 | flag.StringVar(&options.CertDir, "cert-dir", "/tmp/k8s-webhook-server/serving-certs/", "The webhook cert dir") 60 | 61 | logOpts := zap.Options{ 62 | Development: true, 63 | } 64 | logOpts.BindFlags(flag.CommandLine) 65 | flag.Parse() 66 | 67 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&logOpts))) 68 | 69 | webhookServer := webhook.NewServer(options) 70 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 71 | Scheme: scheme, 72 | MetricsBindAddress: metricsAddr, 73 | HealthProbeBindAddress: probeAddr, 74 | WebhookServer: webhookServer, 75 | }) 76 | if err != nil { 77 | setupLog.Error(err, "unable to start webhook server") 78 | os.Exit(1) 79 | } 80 | 81 | if err = (&kasmcloudiov1alpha1.Actor{}).SetupWebhookWithManager(mgr); err != nil { 82 | setupLog.Error(err, "unable to create webhook", "webhook", "Actor") 83 | os.Exit(1) 84 | } 85 | if err = (&kasmcloudiov1alpha1.Provider{}).SetupWebhookWithManager(mgr); err != nil { 86 | setupLog.Error(err, "unable to create webhook", "webhook", "Provider") 87 | os.Exit(1) 88 | } 89 | if err = (&kasmcloudiov1alpha1.Link{}).SetupWebhookWithManager(mgr); err != nil { 90 | setupLog.Error(err, "unable to create webhook", "webhook", "Link") 91 | os.Exit(1) 92 | } 93 | //+kubebuilder:scaffold:builder 94 | 95 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 96 | setupLog.Error(err, "unable to set up health check") 97 | os.Exit(1) 98 | } 99 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 100 | setupLog.Error(err, "unable to set up ready check") 101 | os.Exit(1) 102 | } 103 | 104 | setupLog.Info("starting webhook server") 105 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 106 | setupLog.Error(err, "problem running webhook server") 107 | os.Exit(1) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /crates/apis/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kasmcloud-apis" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | k8s-openapi = { workspace = true, features = ["schemars", "v1_27"]} 10 | kube = { workspace = true, features = ["gzip", "jsonpatch", "runtime", "derive"] } 11 | schemars = { workspace = true } 12 | serde = { workspace = true } 13 | serde_json = { workspace = true } 14 | -------------------------------------------------------------------------------- /crates/apis/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod v1alpha1; 2 | -------------------------------------------------------------------------------- /crates/apis/src/v1alpha1/actor.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use kube::CustomResource; 5 | 6 | #[derive(CustomResource, Clone, Debug, Deserialize, Serialize, JsonSchema)] 7 | #[kube( 8 | group = "kasmcloud.io", 9 | version = "v1alpha1", 10 | kind = "Actor", 11 | namespaced, 12 | status = "ActorStatus", 13 | scale = r#"{"specReplicasPath":".spec.replicas", "statusReplicasPath":".status.replicas"}"# 14 | )] 15 | #[kube(category = "kasmcloud")] 16 | #[serde(rename_all = "camelCase")] 17 | pub struct ActorSpec { 18 | pub host: String, 19 | pub image: String, 20 | pub replicas: i32, 21 | } 22 | 23 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] 24 | #[serde(rename_all = "camelCase")] 25 | pub struct ActorStatus { 26 | pub public_key: String, 27 | pub descriptive_name: Option, 28 | 29 | pub caps: Option>, 30 | pub capability_provider: Option>, 31 | pub call_alias: Option, 32 | pub claims: super::Claims, 33 | 34 | pub version: Option, 35 | pub reversion: Option, 36 | 37 | pub conditions: Vec, 38 | pub replicas: i32, 39 | pub available_replicas: i32, 40 | } 41 | -------------------------------------------------------------------------------- /crates/apis/src/v1alpha1/claims.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use k8s_openapi::apimachinery::pkg::apis::meta::v1 as metav1; 5 | 6 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct Claims { 9 | pub issuer: String, 10 | pub subject: String, 11 | pub issued_at: metav1::Time, 12 | pub not_before: Option, 13 | pub expires: Option, 14 | } 15 | -------------------------------------------------------------------------------- /crates/apis/src/v1alpha1/host.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use kube::CustomResource; 5 | 6 | #[derive(CustomResource, Clone, Debug, Deserialize, Serialize, JsonSchema)] 7 | #[kube( 8 | group = "kasmcloud.io", 9 | version = "v1alpha1", 10 | kind = "KasmCloudHost", 11 | namespaced, 12 | status = "KasmCloudHostStatus" 13 | )] 14 | #[kube(category = "kasmcloud")] 15 | #[serde(rename_all = "camelCase")] 16 | pub struct KasmCloudHostSpec {} 17 | 18 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] 19 | #[serde(rename_all = "camelCase")] 20 | pub struct KasmCloudHostStatus { 21 | pub kube_node_name: String, 22 | pub public_key: String, 23 | pub cluster_public_key: String, 24 | pub cluster_issuers: Vec, 25 | 26 | pub pre_instance: String, 27 | pub instance: String, 28 | 29 | pub providers: HostProviderStatus, 30 | pub actors: HostActorStatus, 31 | } 32 | 33 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] 34 | #[serde(rename_all = "camelCase")] 35 | pub struct HostProviderStatus { 36 | pub count: u64, 37 | } 38 | 39 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] 40 | #[serde(rename_all = "camelCase")] 41 | pub struct HostActorStatus { 42 | pub count: u64, 43 | pub instance_count: u64, 44 | } 45 | -------------------------------------------------------------------------------- /crates/apis/src/v1alpha1/link.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use schemars::JsonSchema; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use kube::CustomResource; 7 | 8 | #[derive(CustomResource, Clone, Debug, Deserialize, Serialize, JsonSchema)] 9 | #[kube( 10 | group = "kasmcloud.io", 11 | version = "v1alpha1", 12 | kind = "Link", 13 | namespaced, 14 | status = "LinkStatus" 15 | )] 16 | #[kube(category = "kasmcloud")] 17 | #[serde(rename_all = "camelCase")] 18 | pub struct LinkSpec { 19 | #[serde(default, skip_serializing_if = "String::is_empty")] 20 | pub link_name: String, 21 | pub provider: Source, 22 | pub actor: Source, 23 | pub contract_id: String, 24 | #[serde(default, skip_serializing_if = "HashMap::is_empty")] 25 | pub values: HashMap, 26 | } 27 | 28 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] 29 | #[serde(rename_all = "camelCase")] 30 | pub struct LinkStatus { 31 | pub provider_key: String, 32 | pub actor_key: String, 33 | pub link_name: String, 34 | 35 | pub conditions: Vec, 36 | } 37 | 38 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] 39 | #[serde(rename_all = "camelCase")] 40 | pub struct Source { 41 | #[serde(default, skip_serializing_if = "String::is_empty")] 42 | pub key: String, 43 | #[serde(default, skip_serializing_if = "String::is_empty")] 44 | pub name: String, 45 | } 46 | -------------------------------------------------------------------------------- /crates/apis/src/v1alpha1/mod.rs: -------------------------------------------------------------------------------- 1 | mod actor; 2 | mod claims; 3 | mod host; 4 | mod link; 5 | mod provider; 6 | 7 | pub use actor::*; 8 | pub use claims::*; 9 | pub use host::*; 10 | pub use link::*; 11 | pub use provider::*; 12 | -------------------------------------------------------------------------------- /crates/apis/src/v1alpha1/provider.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use kube::CustomResource; 5 | 6 | #[derive(CustomResource, Clone, Debug, Deserialize, Serialize, JsonSchema)] 7 | #[kube( 8 | group = "kasmcloud.io", 9 | version = "v1alpha1", 10 | kind = "Provider", 11 | namespaced, 12 | status = "ProviderStatus" 13 | )] 14 | #[kube(category = "kasmcloud")] 15 | #[serde(rename_all = "camelCase")] 16 | pub struct ProviderSpec { 17 | pub host: String, 18 | pub image: String, 19 | pub link: String, 20 | } 21 | 22 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] 23 | #[serde(rename_all = "camelCase")] 24 | pub struct ProviderStatus { 25 | pub public_key: String, 26 | pub contract_id: String, 27 | pub descriptive_name: Option, 28 | 29 | pub claims: super::Claims, 30 | pub architecture_targets: Vec, 31 | 32 | pub vendor: String, 33 | pub version: Option, 34 | pub reversion: Option, 35 | 36 | pub conditions: Vec, 37 | pub instance_id: String, 38 | } 39 | -------------------------------------------------------------------------------- /crates/host/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kasmcloud-host" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = { workspace = true } 10 | async-nats = { workspace = true } 11 | async-trait = { workspace = true } 12 | base64 = { workspace = true } 13 | bytes = { workspace = true } 14 | futures = { workspace = true } 15 | hex = { workspace = true, features = ["std"] } 16 | http = { workspace = true } 17 | k8s-openapi = { workspace = true } 18 | nkeys = { workspace = true } 19 | opentelemetry = { workspace = true } 20 | rmp-serde = { workspace = true } 21 | schemars = { workspace = true } 22 | serde = { workspace = true } 23 | serde_json = { workspace = true } 24 | serde_yaml = { workspace = true } 25 | serde_bytes = { workspace = true } 26 | sha2 = { workspace = true } 27 | thiserror = { workspace = true } 28 | tokio = { workspace = true } 29 | tracing = { workspace = true } 30 | tracing-opentelemetry = { workspace = true } 31 | ulid = { workspace = true } 32 | url = { workspace = true } 33 | uuid = { workspace = true } 34 | wascap = { workspace = true } 35 | wasmcloud-compat = { workspace = true } 36 | wasmcloud-control-interface = { workspace = true } 37 | wasmcloud-core = { workspace = true } 38 | wasmcloud-host = { workspace = true } 39 | wasmcloud-runtime = { workspace = true } 40 | wasmcloud-tracing = { workspace = true } 41 | -------------------------------------------------------------------------------- /crates/host/src/actor.rs: -------------------------------------------------------------------------------- 1 | use core::pin::Pin; 2 | use core::task::{Context, Poll}; 3 | use std::collections::HashMap; 4 | use std::io::Cursor; 5 | use std::sync::Arc; 6 | 7 | use anyhow::{anyhow, Context as _}; 8 | use bytes::{BufMut, Bytes, BytesMut}; 9 | use futures::stream::AbortHandle; 10 | use tokio::io::{stderr, AsyncRead, AsyncWrite}; 11 | use tokio::sync::RwLock; 12 | use tracing::{debug, error, instrument}; 13 | use ulid::Ulid; 14 | 15 | use wasmcloud_core::{ 16 | chunking::{ChunkEndpoint, CHUNK_THRESHOLD_BYTES}, 17 | Invocation, InvocationResponse, WasmCloudEntity, 18 | }; 19 | use wasmcloud_runtime::{capability::IncomingHttp, ActorInstancePool, Runtime}; 20 | 21 | use crate::handler::{ensure_actor_capability, Handler}; 22 | 23 | pub struct ActorGroup { 24 | pub public_key: String, 25 | 26 | pub actors: RwLock>>, 27 | pub links: Arc>>>, 28 | } 29 | 30 | pub struct Actor { 31 | pub image_ref: String, 32 | pub handler: Handler, 33 | pub pool: ActorInstancePool, 34 | 35 | pub instances: RwLock>>, 36 | } 37 | 38 | pub struct ActorInstance { 39 | pub id: Ulid, 40 | pub runtime: Runtime, 41 | pub handler: Handler, 42 | pub pool: ActorInstancePool, 43 | pub nats: async_nats::Client, 44 | pub valid_issuers: Vec, 45 | pub chunk_endpoint: ChunkEndpoint, 46 | pub calls: AbortHandle, 47 | } 48 | 49 | impl ActorInstance { 50 | #[instrument(skip(self, msg))] 51 | async fn handle_invocation( 52 | &self, 53 | contract_id: &str, 54 | operation: &str, 55 | msg: Vec, 56 | ) -> anyhow::Result, String>> { 57 | ensure_actor_capability(self.handler.claims.metadata.as_ref(), contract_id)?; 58 | 59 | let mut instance = self 60 | .pool 61 | .instantiate(self.runtime.clone()) 62 | .await 63 | .context("failed to instantiate actor")?; 64 | instance 65 | .stderr(stderr()) 66 | .await 67 | .context("failed to set stderr")? 68 | .blobstore(Arc::new(self.handler.clone())) 69 | .bus(Arc::new(self.handler.clone())) 70 | .keyvalue_atomic(Arc::new(self.handler.clone())) 71 | .keyvalue_readwrite(Arc::new(self.handler.clone())) 72 | .messaging(Arc::new(self.handler.clone())); 73 | 74 | match (contract_id, operation) { 75 | ("wasmcloud:httpserver", "HttpServer.HandleRequest") => { 76 | let req: wasmcloud_compat::HttpRequest = 77 | rmp_serde::from_slice(&msg).context("failed to decode HTTP request")?; 78 | let req = http::Request::try_from(req).context("failed to convert request")?; 79 | 80 | let res = match wasmcloud_runtime::ActorInstance::from(instance) 81 | .into_incoming_http() 82 | .await 83 | .context("failed to instantiate `wasi:http/incoming-handler`")? 84 | .handle(req.map(|body| -> Box { 85 | Box::new(Cursor::new(body)) 86 | })) 87 | .await 88 | { 89 | Ok(res) => res, 90 | Err(err) => return Ok(Err(format!("{err:#}"))), 91 | }; 92 | let res = wasmcloud_compat::HttpResponse::from_http(res) 93 | .await 94 | .context("failed to convert response")?; 95 | let res = rmp_serde::to_vec_named(&res).context("failed to encode response")?; 96 | Ok(Ok(res)) 97 | } 98 | _ => { 99 | let res = AsyncBytesMut::default(); 100 | match instance 101 | .call(operation, Cursor::new(msg), res.clone()) 102 | .await 103 | .context("failed to call actor")? 104 | { 105 | Ok(()) => { 106 | let res = res.try_into().context("failed to unwrap bytes")?; 107 | Ok(Ok(res)) 108 | } 109 | Err(e) => Ok(Err(e)), 110 | } 111 | } 112 | } 113 | } 114 | 115 | #[instrument(skip_all)] 116 | async fn handle_call(&self, payload: impl AsRef<[u8]>) -> anyhow::Result { 117 | let invocation: Invocation = 118 | rmp_serde::from_slice(payload.as_ref()).context("failed to decode invocation")?; 119 | 120 | invocation.validate_antiforgery(&self.valid_issuers)?; 121 | 122 | let content_length: usize = invocation 123 | .content_length 124 | .try_into() 125 | .context("failed to convert content_length to usize")?; 126 | 127 | let inv_msg = if content_length > CHUNK_THRESHOLD_BYTES { 128 | self.chunk_endpoint.get_unchunkified(&invocation.id).await? 129 | } else { 130 | invocation.msg 131 | }; 132 | 133 | let res = match self 134 | .handle_invocation( 135 | &invocation.origin.contract_id, 136 | &invocation.operation, 137 | inv_msg, 138 | ) 139 | .await 140 | .context("failed to handle invocation")? 141 | { 142 | Ok(resp_msg) => { 143 | let content_length = resp_msg.len(); 144 | let resp_msg = if content_length > CHUNK_THRESHOLD_BYTES { 145 | debug!(inv_id = invocation.id, "chunking invocation response"); 146 | self.chunk_endpoint 147 | .chunkify_response(&invocation.id, Cursor::new(resp_msg)) 148 | .await 149 | .context("failed to chunk invocation response")?; 150 | vec![] 151 | } else { 152 | resp_msg 153 | }; 154 | 155 | InvocationResponse { 156 | msg: resp_msg, 157 | invocation_id: invocation.id, 158 | content_length: content_length as u64, 159 | ..Default::default() 160 | } 161 | } 162 | Err(e) => InvocationResponse { 163 | invocation_id: invocation.id, 164 | error: Some(e), 165 | ..Default::default() 166 | }, 167 | }; 168 | 169 | rmp_serde::to_vec_named(&res) 170 | .map(Into::into) 171 | .context("failed to encode response") 172 | } 173 | 174 | #[instrument(skip_all)] 175 | pub async fn handle_message( 176 | &self, 177 | async_nats::Message { 178 | reply, 179 | payload, 180 | subject, 181 | .. 182 | }: async_nats::Message, 183 | ) { 184 | let res = self.handle_call(payload).await; 185 | match (reply, res) { 186 | (Some(reply), Ok(buf)) => { 187 | if let Err(e) = self.nats.publish(reply, buf).await { 188 | error!("failed to publish response to `{subject}` request: {e:?}"); 189 | } 190 | } 191 | (_, Err(e)) => { 192 | error!("failed to handle `{subject}` request: {e:?}"); 193 | } 194 | _ => {} 195 | } 196 | } 197 | } 198 | 199 | #[derive(Clone, Default)] 200 | struct AsyncBytesMut(Arc>); 201 | 202 | impl AsyncWrite for AsyncBytesMut { 203 | fn poll_write( 204 | self: Pin<&mut Self>, 205 | _cx: &mut Context<'_>, 206 | buf: &[u8], 207 | ) -> Poll> { 208 | Poll::Ready({ 209 | self.0 210 | .lock() 211 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))? 212 | .put_slice(buf); 213 | Ok(buf.len()) 214 | }) 215 | } 216 | 217 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 218 | Poll::Ready(Ok(())) 219 | } 220 | 221 | fn poll_shutdown( 222 | self: Pin<&mut Self>, 223 | _cx: &mut Context<'_>, 224 | ) -> Poll> { 225 | Poll::Ready(Ok(())) 226 | } 227 | } 228 | 229 | impl TryFrom for Vec { 230 | type Error = anyhow::Error; 231 | 232 | fn try_from(buf: AsyncBytesMut) -> Result { 233 | buf.0 234 | .lock() 235 | .map(|buf| buf.clone().into()) 236 | .map_err(|e| anyhow!(e.to_string()).context("failed to lock")) 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /crates/host/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod actor; 2 | mod handler; 3 | mod provider; 4 | 5 | use core::num::NonZeroUsize; 6 | use core::time::Duration; 7 | use std::collections::{hash_map, HashMap}; 8 | use std::env; 9 | use std::env::consts::{ARCH, FAMILY, OS}; 10 | use std::process::Stdio; 11 | use std::sync::Arc; 12 | 13 | use anyhow::{anyhow, bail, ensure, Context as _}; 14 | use base64::engine::general_purpose::STANDARD; 15 | use base64::Engine; 16 | use futures::stream::{AbortHandle, Abortable}; 17 | use futures::{stream, try_join, StreamExt, TryStreamExt}; 18 | use nkeys::{KeyPair, KeyPairType}; 19 | use serde_json::json; 20 | use sha2::{Digest, Sha256}; 21 | use tokio::io::AsyncWriteExt; 22 | use tokio::sync::RwLock; 23 | use tokio::{process, spawn}; 24 | use tracing::{error, info, instrument}; 25 | use ulid::Ulid; 26 | use url::Url; 27 | use uuid::Uuid; 28 | 29 | use wascap::jwt; 30 | use wasmcloud_control_interface::LinkDefinition; 31 | use wasmcloud_core::{chunking::ChunkEndpoint, HostData, OtelConfig, WasmCloudEntity}; 32 | use wasmcloud_host::{policy::RequestTarget, RegistryConfig}; 33 | use wasmcloud_runtime::{ActorInstancePool, Runtime}; 34 | 35 | use handler::Handler; 36 | 37 | #[derive(Clone)] 38 | pub struct NatsConfig { 39 | pub url: Url, 40 | /// Timeout period for all RPC calls 41 | pub timeout: Option, 42 | /// Authentication JWT for RPC connection, must be specified with rpc_seed 43 | pub jwt: Option, 44 | /// Authentication key pair for RPC connection, must be specified with rpc_jwt 45 | pub key: Option>, 46 | /// Whether to require TLS for RPC connection 47 | pub tls: bool, 48 | } 49 | 50 | async fn connect_nats(config: NatsConfig) -> anyhow::Result { 51 | let opts = async_nats::ConnectOptions::new().require_tls(config.tls); 52 | let opts = match (config.jwt, config.key) { 53 | (Some(jwt), Some(key)) => opts.jwt(jwt.to_string(), { 54 | move |nonce| { 55 | let key = key.clone(); 56 | async move { key.sign(&nonce).map_err(async_nats::AuthError::new) } 57 | } 58 | }), 59 | (Some(_), None) | (None, Some(_)) => { 60 | bail!("cannot authenticate if only one of jwt or seed is specified") 61 | } 62 | _ => opts, 63 | }; 64 | let opts = if let Some(timeout) = config.timeout { 65 | opts.request_timeout(Some(timeout)) 66 | } else { 67 | opts 68 | }; 69 | opts.connect(config.url.as_str()) 70 | .await 71 | .context("failed to connect to NATS") 72 | } 73 | 74 | #[derive(Clone)] 75 | pub struct HostConfig { 76 | pub allow_file_load: bool, 77 | 78 | pub rpc_config: NatsConfig, 79 | pub prov_rpc_config: NatsConfig, 80 | 81 | /// The lattice the host belongs to 82 | pub lattice_prefix: String, 83 | /// The domain to use for host Jetstream operations 84 | pub js_domain: Option, 85 | 86 | /// The server key pair used by this host to generate its public key 87 | pub host_key: Option>, 88 | /// The cluster key pair used by this host to sign all invocations 89 | pub cluster_key: Option>, 90 | /// The identity keys (a printable 256-bit Ed25519 public key) that this host should allow invocations from 91 | pub cluster_issuers: Option>, 92 | 93 | pub log_level: Option, 94 | } 95 | 96 | pub struct HostStats { 97 | pub provider_count: u64, 98 | pub actor_count: u64, 99 | pub actor_instance_count: u64, 100 | } 101 | 102 | pub struct Host { 103 | pub name: String, 104 | pub host_key: Arc, 105 | pub lattice_prefix: String, 106 | pub cluster_key: Arc, 107 | pub cluster_issuers: Vec, 108 | 109 | config: HostConfig, 110 | runtime: Runtime, 111 | rpc_nats: async_nats::Client, 112 | prov_rpc_nats: async_nats::Client, 113 | chunk_endpoint: ChunkEndpoint, 114 | 115 | // name -> LinkDefinition 116 | links: RwLock>, 117 | 118 | // name -> public key 119 | actor_keys: RwLock>, 120 | 121 | // public key -> ActorGroup 122 | actors: RwLock>>, 123 | 124 | // image_ref -> Provider 125 | providers: RwLock>, 126 | 127 | aliases: Arc>>, 128 | registry_settings: RwLock>, 129 | } 130 | 131 | impl Host { 132 | pub async fn new(name: String, config: HostConfig) -> anyhow::Result { 133 | let cluster_key = if let Some(cluster_key) = &config.cluster_key { 134 | ensure!(cluster_key.key_pair_type() == KeyPairType::Cluster); 135 | Arc::clone(cluster_key) 136 | } else { 137 | Arc::new(KeyPair::new(KeyPairType::Cluster)) 138 | }; 139 | 140 | let mut cluster_issuers = config.cluster_issuers.clone().unwrap_or_default(); 141 | if !cluster_issuers.contains(&cluster_key.public_key()) { 142 | cluster_issuers.push(cluster_key.public_key()); 143 | } 144 | 145 | let host_key = if let Some(host_key) = &config.host_key { 146 | ensure!(host_key.key_pair_type() == KeyPairType::Server); 147 | Arc::clone(host_key) 148 | } else { 149 | Arc::new(KeyPair::new(KeyPairType::Server)) 150 | }; 151 | 152 | println!("cluster key public: {:?}", cluster_key.public_key()); 153 | println!("cluster seed: {:?}", cluster_key.seed()); 154 | println!("host key public: {:?}", host_key.public_key()); 155 | println!("host seed: {:?}", host_key.seed()); 156 | 157 | let runtime = Runtime::builder() 158 | .actor_config(wasmcloud_runtime::ActorConfig { 159 | require_signature: true, 160 | }) 161 | .build() 162 | .context("failed to build runtime")?; 163 | 164 | let mut labels = HashMap::from([ 165 | ("hostcore.arch".into(), ARCH.into()), 166 | ("hostcore.os".into(), OS.into()), 167 | ("hostcore.osfamily".into(), FAMILY.into()), 168 | ]); 169 | labels.extend(env::vars().filter_map(|(k, v)| { 170 | let k = k.strip_prefix("HOST_")?; 171 | Some((k.to_lowercase(), v)) 172 | })); 173 | 174 | let (rpc_nats, prov_rpc_nats) = try_join!( 175 | async { 176 | let rpc_nats_url = config.rpc_config.url.as_str(); 177 | info!("connecting to NATS RPC server: {rpc_nats_url}"); 178 | connect_nats(config.rpc_config.clone()) 179 | .await 180 | .context("failed to establish NATS RPC server connection") 181 | }, 182 | async { 183 | let prov_rpc_nats_url = config.prov_rpc_config.url.as_str(); 184 | info!("connecting to NATS Provider RPC server: {prov_rpc_nats_url}"); 185 | connect_nats(config.prov_rpc_config.clone()) 186 | .await 187 | .context("failed to establish NATS provider RPC server connection") 188 | } 189 | )?; 190 | 191 | let chunk_endpoint = ChunkEndpoint::with_client( 192 | &config.lattice_prefix, 193 | rpc_nats.clone(), 194 | config.js_domain.as_ref(), 195 | ); 196 | 197 | Ok(Host { 198 | name, 199 | host_key, 200 | cluster_key, 201 | cluster_issuers, 202 | lattice_prefix: config.lattice_prefix.clone(), 203 | 204 | runtime, 205 | rpc_nats, 206 | prov_rpc_nats, 207 | chunk_endpoint, 208 | config, 209 | 210 | actors: RwLock::default(), 211 | actor_keys: RwLock::default(), 212 | providers: RwLock::default(), 213 | links: RwLock::default(), 214 | aliases: Arc::default(), 215 | registry_settings: RwLock::new(HashMap::new()), 216 | }) 217 | } 218 | 219 | pub async fn stats(&self) -> HostStats { 220 | let actors = self.actors.read().await; 221 | let actor_count = actors.len(); 222 | let mut instance_count: usize = 0; 223 | for (_, group) in &*actors { 224 | for (_, actor) in &*group.actors.read().await { 225 | instance_count += actor.instances.read().await.len(); 226 | } 227 | } 228 | 229 | HostStats { 230 | actor_count: actor_count as u64, 231 | actor_instance_count: instance_count as u64, 232 | provider_count: self.providers.read().await.len() as u64, 233 | } 234 | } 235 | 236 | #[instrument(skip_all)] 237 | async fn fetch_actor(&self, actor_ref: &str) -> anyhow::Result { 238 | let setting = self.registry_settings.read().await; 239 | let actor = wasmcloud_host::fetch_actor(actor_ref, self.config.allow_file_load, &setting) 240 | .await 241 | .context("failed to fetch actor")?; 242 | 243 | let actor = wasmcloud_runtime::Actor::new(&self.runtime, actor) 244 | .context("failed to initialize actor")?; 245 | Ok(actor) 246 | } 247 | 248 | #[instrument(skip(self))] 249 | pub async fn reconcile_actor( 250 | &self, 251 | name: String, 252 | actor_ref: String, 253 | replica: usize, 254 | ) -> anyhow::Result> { 255 | let actor = self.fetch_actor(&actor_ref).await?; 256 | let claims = actor.claims().context("claims missing")?.clone(); 257 | 258 | if let Some(public_key) = self.actor_keys.read().await.get(&name) { 259 | if public_key != &claims.subject { 260 | return Err(anyhow!("actor public key is change")); 261 | } 262 | } 263 | 264 | if let hash_map::Entry::Vacant(entry) = self.actor_keys.write().await.entry(name.clone()) { 265 | entry.insert(claims.subject.clone()); 266 | } 267 | 268 | let group = match self.actors.write().await.entry(claims.subject.clone()) { 269 | hash_map::Entry::Vacant(entry) => { 270 | let links = self.get_actor_links(claims.subject.clone()).await; 271 | let group = Arc::new(actor::ActorGroup { 272 | public_key: claims.subject.clone(), 273 | actors: RwLock::default(), 274 | links: Arc::new(RwLock::new(links)), 275 | }); 276 | entry.insert(group.clone()); 277 | group.clone() 278 | } 279 | hash_map::Entry::Occupied(entry) => Arc::clone(entry.get()), 280 | }; 281 | match group.actors.write().await.entry(name.clone()) { 282 | hash_map::Entry::Vacant(entry) => { 283 | if let Some(replica) = NonZeroUsize::new(replica) { 284 | let actor = self 285 | .start_actor(actor, actor_ref, replica, group.links.clone()) 286 | .await 287 | .context("failed to start actor")?; 288 | entry.insert(actor); 289 | } 290 | } 291 | hash_map::Entry::Occupied(entry) => { 292 | let actor = entry.get(); 293 | 294 | let mut instances = actor.instances.write().await; 295 | let current = instances.len(); 296 | if let Some(delta) = replica.checked_sub(current) { 297 | if let Some(delta) = NonZeroUsize::new(delta) { 298 | let mut delta = self 299 | .instantiate_actor( 300 | &claims, 301 | &actor.image_ref, 302 | delta, 303 | actor.pool.clone(), 304 | actor.handler.clone(), 305 | ) 306 | .await 307 | .context("failed to instantiate actor")?; 308 | instances.append(&mut delta); 309 | } 310 | } else if let Some(delta) = current.checked_sub(replica) { 311 | info!("stop {delta} actor instances"); 312 | stream::iter(instances.drain(..delta)) 313 | .map(Ok) 314 | .try_for_each_concurrent(None, |instance| { 315 | instance.calls.abort(); 316 | async { Result::<(), anyhow::Error>::Ok(()) } 317 | }) 318 | .await 319 | .context("failed to uninstantiate actor")?; 320 | }; 321 | } 322 | } 323 | Ok(claims) 324 | } 325 | 326 | #[instrument(skip(self))] 327 | pub async fn remove_actor(&self, name: String) -> anyhow::Result<()> { 328 | let public_key = if let Some(key) = self.actor_keys.read().await.get(&name) { 329 | key.clone() 330 | } else { 331 | info!("remove actor success"); 332 | return Ok(()); 333 | }; 334 | 335 | if let hash_map::Entry::Occupied(entry) = 336 | self.actors.write().await.entry(public_key.clone()) 337 | { 338 | let group = entry.get(); 339 | if let hash_map::Entry::Occupied(entry) = group.actors.write().await.entry(name.clone()) 340 | { 341 | let actor = entry.get(); 342 | let mut instances = actor.instances.write().await; 343 | let count = instances.len(); 344 | stream::iter(instances.drain(..count)) 345 | .map(Ok) 346 | .try_for_each_concurrent(None, |instance| { 347 | instance.calls.abort(); 348 | async { Result::<(), anyhow::Error>::Ok(()) } 349 | }) 350 | .await 351 | .context("failed to uninstantiate actor")?; 352 | 353 | drop(instances); 354 | entry.remove(); 355 | } 356 | 357 | if group.actors.read().await.is_empty() { 358 | entry.remove(); 359 | info!("all actors({public_key}) is removed"); 360 | } 361 | }; 362 | 363 | self.actor_keys.write().await.remove(&name); 364 | info!("remove actor success"); 365 | Ok(()) 366 | } 367 | 368 | #[instrument(skip_all)] 369 | pub async fn start_actor( 370 | &self, 371 | actor: wasmcloud_runtime::Actor, 372 | actor_ref: String, 373 | replica: NonZeroUsize, 374 | links: Arc>>>, 375 | ) -> anyhow::Result> { 376 | let claims = actor.claims().context("claims missing")?; 377 | let origin = WasmCloudEntity { 378 | public_key: claims.subject.clone(), 379 | ..Default::default() 380 | }; 381 | 382 | let handler = Handler { 383 | lattice_prefix: self.lattice_prefix.to_owned(), 384 | host_key: Arc::clone(&self.host_key), 385 | cluster_key: Arc::clone(&self.cluster_key), 386 | 387 | origin, 388 | claims: claims.clone(), 389 | nats: self.rpc_nats.clone(), 390 | chunk_endpoint: self.chunk_endpoint.clone(), 391 | 392 | links, 393 | targets: Arc::new(RwLock::default()), 394 | aliases: Arc::clone(&self.aliases), 395 | }; 396 | 397 | let pool = ActorInstancePool::new(actor.clone(), Some(replica.clone())); 398 | let instances = self 399 | .instantiate_actor(claims, &actor_ref, replica, pool.clone(), handler.clone()) 400 | .await 401 | .context("failed to instantiate actor")?; 402 | 403 | Ok(Arc::new(actor::Actor { 404 | image_ref: actor_ref, 405 | pool, 406 | instances: RwLock::new(instances), 407 | handler, 408 | })) 409 | } 410 | 411 | #[instrument(skip_all)] 412 | pub async fn instantiate_actor( 413 | &self, 414 | claims: &jwt::Claims, 415 | actor_ref: impl AsRef, 416 | count: NonZeroUsize, 417 | pool: ActorInstancePool, 418 | handler: Handler, 419 | ) -> anyhow::Result>> { 420 | let actor_ref = actor_ref.as_ref(); 421 | info!( 422 | subject = claims.subject, 423 | actor_ref = actor_ref, 424 | "instantiate {count} actor instances", 425 | ); 426 | let instances = futures::stream::repeat(format!( 427 | "wasmbus.rpc.{lattice_prefix}.{subject}", 428 | lattice_prefix = self.lattice_prefix, 429 | subject = claims.subject 430 | )) 431 | .take(count.into()) 432 | .then(|topic| { 433 | let pool = pool.clone(); 434 | let handler = handler.clone(); 435 | async move { 436 | let calls = self 437 | .rpc_nats 438 | .queue_subscribe(topic.clone(), topic) 439 | .await 440 | .context("failed to subscribe to actor call queue")?; 441 | 442 | let (calls_abort, calls_abort_reg) = AbortHandle::new_pair(); 443 | let id = Ulid::new(); 444 | 445 | let instance = Arc::new(actor::ActorInstance { 446 | nats: self.rpc_nats.clone(), 447 | pool, 448 | id, 449 | calls: calls_abort, 450 | runtime: self.runtime.clone(), 451 | handler: handler.clone(), 452 | chunk_endpoint: self.chunk_endpoint.clone(), 453 | valid_issuers: self.cluster_issuers.clone(), 454 | }); 455 | 456 | let _calls = spawn({ 457 | let instance = Arc::clone(&instance); 458 | Abortable::new(calls, calls_abort_reg).for_each_concurrent(None, move |msg| { 459 | let instance = Arc::clone(&instance); 460 | async move { instance.handle_message(msg).await } 461 | }) 462 | }); 463 | anyhow::Result::<_>::Ok(instance) 464 | } 465 | }) 466 | .try_collect() 467 | .await 468 | .context("failed to instantiate actor")?; 469 | 470 | Ok(instances) 471 | } 472 | 473 | #[instrument(skip(self))] 474 | pub async fn start_provider( 475 | &self, 476 | link_name: String, 477 | provider_ref: String, 478 | ) -> anyhow::Result> { 479 | let registry_setting = self.registry_settings.read().await; 480 | info!("wait fetch provider image"); 481 | let (path, claims) = wasmcloud_host::fetch_provider( 482 | &provider_ref, 483 | &link_name, 484 | self.config.allow_file_load, 485 | ®istry_setting, 486 | ) 487 | .await 488 | .context("failed to fetch provider")?; 489 | 490 | let mut target = RequestTarget::from(claims.clone()); 491 | target.link_name = Some(link_name.clone()); 492 | 493 | let mut providers = self.providers.write().await; 494 | let provider::Provider { instances, .. } = providers 495 | .entry(claims.subject.clone()) 496 | .or_insert(provider::Provider { 497 | claims: claims.clone(), 498 | image_ref: provider_ref.into(), 499 | instances: HashMap::default(), 500 | }); 501 | 502 | if let hash_map::Entry::Vacant(entry) = instances.entry(link_name.clone()) { 503 | let id = Ulid::new(); 504 | let invocation_seed = self 505 | .cluster_key 506 | .seed() 507 | .context("cluster key seed missing")?; 508 | 509 | let links = self.links.read().await; 510 | let link_definitions: Vec<_> = links 511 | .clone() 512 | .into_values() 513 | .filter(|ld| ld.provider_id == claims.subject && ld.link_name == link_name) 514 | .map(|ld| wasmcloud_core::LinkDefinition { 515 | actor_id: ld.actor_id, 516 | provider_id: ld.provider_id, 517 | link_name: ld.link_name, 518 | contract_id: ld.contract_id, 519 | values: ld.values.into_iter().collect(), 520 | }) 521 | .collect(); 522 | 523 | let lattice_rpc_user_seed = self 524 | .config 525 | .prov_rpc_config 526 | .key 527 | .as_ref() 528 | .map(|key| key.seed()) 529 | .transpose() 530 | .context("private key missing for provider RPC key")?; 531 | let default_rpc_timeout_ms = Some( 532 | self.config 533 | .rpc_config 534 | .timeout 535 | .unwrap_or_default() 536 | .as_millis() 537 | .try_into() 538 | .context("failed to convert rpc_timeout to u64")?, 539 | ); 540 | 541 | let host_data = HostData { 542 | host_id: self.host_key.public_key(), 543 | lattice_rpc_prefix: self.config.lattice_prefix.clone(), 544 | link_name: link_name.to_string(), 545 | lattice_rpc_user_jwt: self.config.prov_rpc_config.jwt.clone().unwrap_or_default(), 546 | lattice_rpc_user_seed: lattice_rpc_user_seed.unwrap_or_default(), 547 | lattice_rpc_url: self.config.prov_rpc_config.url.to_string(), 548 | env_values: vec![], 549 | instance_id: Uuid::from_u128(id.into()).to_string(), 550 | config_json: None, 551 | provider_key: claims.subject.clone(), 552 | link_definitions, 553 | default_rpc_timeout_ms, 554 | cluster_issuers: self.cluster_issuers.clone(), 555 | invocation_seed, 556 | log_level: self.config.log_level.clone(), 557 | structured_logging: false, 558 | otel_config: OtelConfig { 559 | traces_exporter: None, 560 | exporter_otlp_endpoint: None, 561 | }, 562 | }; 563 | let host_data = 564 | serde_json::to_vec(&host_data).context("failed to serialize provider data")?; 565 | 566 | let mut child = process::Command::new(&path) 567 | .env_clear() 568 | .stdin(Stdio::piped()) 569 | .kill_on_drop(true) 570 | .spawn() 571 | .context("failed to spawn provider process")?; 572 | let mut stdin = child.stdin.take().context("failed to take stdin")?; 573 | stdin 574 | .write_all(STANDARD.encode(&host_data).as_bytes()) 575 | .await 576 | .context("failed to write provider data")?; 577 | stdin 578 | .write_all(b"\r\n") 579 | .await 580 | .context("failed to write newline")?; 581 | stdin.shutdown().await.context("failed to close stdin")?; 582 | 583 | entry.insert(provider::ProviderInstance { id, child }); 584 | info!("run provider, instance_id: {id}"); 585 | } else { 586 | info!("provider is running"); 587 | } 588 | Ok(claims.clone()) 589 | } 590 | 591 | #[instrument(skip(self))] 592 | pub async fn stop_provider(&self, link_name: String, key: String) -> anyhow::Result<()> { 593 | let mut providers = self.providers.write().await; 594 | let hash_map::Entry::Occupied(mut entry) = providers.entry( key.clone()) else { 595 | info!("provider is stopped"); 596 | return Ok(()); 597 | }; 598 | 599 | let provider = entry.get_mut(); 600 | let instances = &mut provider.instances; 601 | if let hash_map::Entry::Occupied(entry) = instances.entry(link_name.clone()) { 602 | entry.remove(); 603 | 604 | info!("send gracefully shut down requsest"); 605 | if let Ok(payload) = 606 | serde_json::to_vec(&json!({ "host_id": self.host_key.public_key()})) 607 | { 608 | if let Err(e) = self 609 | .prov_rpc_nats 610 | .send_request( 611 | format!( 612 | "wasmbus.rpc.{}.{key}.{link_name}.shutdown", 613 | self.config.lattice_prefix 614 | ), 615 | async_nats::Request::new() 616 | .payload(payload.into()) 617 | .timeout(Some(Duration::from_secs(5))), 618 | ) 619 | .await 620 | { 621 | error!( 622 | "Provider didn't gracefully shut down in time, shuting down forcefully: {e}" 623 | ); 624 | } 625 | } 626 | } 627 | if instances.is_empty() { 628 | info!("don't have provider({key})'s instances"); 629 | entry.remove(); 630 | } 631 | 632 | Ok(()) 633 | } 634 | 635 | pub async fn get_actor_links( 636 | &self, 637 | key: String, 638 | ) -> HashMap> { 639 | self.links 640 | .read() 641 | .await 642 | .values() 643 | .filter(|ld| ld.actor_id == key) 644 | .fold( 645 | HashMap::<_, HashMap<_, _>>::default(), 646 | |mut links, 647 | LinkDefinition { 648 | link_name, 649 | contract_id, 650 | provider_id, 651 | .. 652 | }| { 653 | links.entry(contract_id.clone()).or_default().insert( 654 | link_name.clone(), 655 | WasmCloudEntity { 656 | link_name: link_name.clone(), 657 | contract_id: contract_id.clone(), 658 | public_key: provider_id.clone(), 659 | }, 660 | ); 661 | links 662 | }, 663 | ) 664 | } 665 | 666 | #[instrument(skip(self))] 667 | pub async fn add_linkdef(&self, ld: LinkDefinition) -> anyhow::Result<()> { 668 | let id = linkdef_hash(&ld.actor_id, &ld.contract_id, &ld.link_name); 669 | if let hash_map::Entry::Vacant(entry) = self.links.write().await.entry(id.clone()) { 670 | entry.insert(ld.clone()); 671 | } else { 672 | info!("skip linkdef"); 673 | return Ok(()); 674 | } 675 | 676 | info!("add linkdef"); 677 | if let Some(actor) = self.actors.read().await.get(&ld.actor_id) { 678 | let mut links = actor.links.write().await; 679 | 680 | links.entry(ld.contract_id.clone()).or_default().insert( 681 | ld.link_name.clone(), 682 | WasmCloudEntity { 683 | public_key: ld.provider_id.clone(), 684 | link_name: ld.link_name.clone(), 685 | contract_id: ld.contract_id.clone(), 686 | }, 687 | ); 688 | } 689 | 690 | let msgp = rmp_serde::to_vec(&ld).context("failed to encode link definition")?; 691 | let lattice_prefix = &self.config.lattice_prefix; 692 | self.prov_rpc_nats 693 | .publish( 694 | format!( 695 | "wasmbus.rpc.{lattice_prefix}.{}.{}.linkdefs.put", 696 | ld.provider_id, ld.link_name 697 | ), 698 | msgp.into(), 699 | ) 700 | .await 701 | .context("failed to publish link deinition")?; 702 | 703 | Ok(()) 704 | } 705 | 706 | #[instrument(skip(self))] 707 | pub async fn delete_linkdef(&self, ld: LinkDefinition) -> anyhow::Result<()> { 708 | let id = linkdef_hash(&ld.actor_id, &ld.contract_id, &ld.link_name); 709 | 710 | info!("delete linkdef"); 711 | let ref ld @ LinkDefinition { 712 | ref actor_id, 713 | ref provider_id, 714 | ref contract_id, 715 | ref link_name, 716 | .. 717 | } = self 718 | .links 719 | .write() 720 | .await 721 | .remove(&id) 722 | .context("attempt to remove a non-exitent link")?; 723 | 724 | if let Some(actor) = self.actors.read().await.get(actor_id) { 725 | let mut links = actor.links.write().await; 726 | if let Some(links) = links.get_mut(contract_id) { 727 | links.remove(link_name); 728 | } 729 | } 730 | 731 | let msgp: Vec = rmp_serde::to_vec(ld).context("failed to encode link definition")?; 732 | let lattice_prefix: &String = &self.config.lattice_prefix; 733 | self.prov_rpc_nats 734 | .publish( 735 | format!("wasmbus.rpc.{lattice_prefix}.{provider_id}.{link_name}.linkdefs.del",), 736 | msgp.into(), 737 | ) 738 | .await 739 | .context("failed to publish link definition deletion")?; 740 | Ok(()) 741 | } 742 | } 743 | 744 | fn linkdef_hash( 745 | actor_id: impl AsRef, 746 | contract_id: impl AsRef, 747 | link_name: impl AsRef, 748 | ) -> String { 749 | let mut hash = Sha256::default(); 750 | hash.update(actor_id.as_ref()); 751 | hash.update(contract_id.as_ref()); 752 | hash.update(link_name.as_ref()); 753 | hex::encode_upper(hash.finalize()) 754 | } 755 | -------------------------------------------------------------------------------- /crates/host/src/provider.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use tokio::process::Child; 4 | use ulid::Ulid; 5 | 6 | use wascap::jwt; 7 | 8 | pub struct Provider { 9 | pub claims: jwt::Claims, 10 | // link name -> ProviderInstance 11 | pub instances: HashMap, 12 | pub image_ref: String, 13 | } 14 | 15 | pub struct ProviderInstance { 16 | pub child: Child, 17 | pub id: Ulid, 18 | } 19 | -------------------------------------------------------------------------------- /deploy/crds/kasmcloud.io_actors.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.12.0 7 | name: actors.kasmcloud.io 8 | spec: 9 | group: kasmcloud.io 10 | names: 11 | categories: 12 | - kasmcloud 13 | kind: Actor 14 | listKind: ActorList 15 | plural: actors 16 | singular: actor 17 | scope: Namespaced 18 | versions: 19 | - additionalPrinterColumns: 20 | - jsonPath: .status.descriptiveName 21 | name: Desc 22 | type: string 23 | - jsonPath: .status.publicKey 24 | name: PublicKey 25 | type: string 26 | - jsonPath: .spec.replicas 27 | name: Replicas 28 | type: integer 29 | - jsonPath: .status.availableReplicas 30 | name: AvailableReplicas 31 | type: integer 32 | - jsonPath: .status.capabilityProvider 33 | name: Caps 34 | type: string 35 | - jsonPath: .spec.image 36 | name: Image 37 | type: string 38 | name: v1alpha1 39 | schema: 40 | openAPIV3Schema: 41 | description: Actor is the Schema for the actors API 42 | properties: 43 | apiVersion: 44 | description: 'APIVersion defines the versioned schema of this representation 45 | of an object. Servers should convert recognized schemas to the latest 46 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 47 | type: string 48 | kind: 49 | description: 'Kind is a string value representing the REST resource this 50 | object represents. Servers may infer this from the endpoint the client 51 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 52 | type: string 53 | metadata: 54 | type: object 55 | spec: 56 | description: ActorSpec defines the desired state of Actor 57 | properties: 58 | host: 59 | minLength: 1 60 | type: string 61 | image: 62 | minLength: 1 63 | type: string 64 | replicas: 65 | type: integer 66 | required: 67 | - host 68 | - image 69 | - replicas 70 | type: object 71 | status: 72 | description: ActorStatus defines the observed state of Actor 73 | properties: 74 | availableReplicas: 75 | type: integer 76 | callAlias: 77 | type: string 78 | capabilityProvider: 79 | items: 80 | type: string 81 | type: array 82 | caps: 83 | items: 84 | type: string 85 | type: array 86 | claims: 87 | properties: 88 | expires: 89 | format: date-time 90 | type: string 91 | issuedAt: 92 | format: date-time 93 | type: string 94 | issuer: 95 | type: string 96 | notBefore: 97 | format: date-time 98 | type: string 99 | subject: 100 | type: string 101 | required: 102 | - issuedAt 103 | - issuer 104 | - subject 105 | type: object 106 | conditions: 107 | items: 108 | description: "Condition contains details for one aspect of the current 109 | state of this API Resource. --- This struct is intended for direct 110 | use as an array at the field path .status.conditions. For example, 111 | \n type FooStatus struct{ // Represents the observations of a 112 | foo's current state. // Known .status.conditions.type are: \"Available\", 113 | \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge 114 | // +listType=map // +listMapKey=type Conditions []metav1.Condition 115 | `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" 116 | protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" 117 | properties: 118 | lastTransitionTime: 119 | description: lastTransitionTime is the last time the condition 120 | transitioned from one status to another. This should be when 121 | the underlying condition changed. If that is not known, then 122 | using the time when the API field changed is acceptable. 123 | format: date-time 124 | type: string 125 | message: 126 | description: message is a human readable message indicating 127 | details about the transition. This may be an empty string. 128 | maxLength: 32768 129 | type: string 130 | observedGeneration: 131 | description: observedGeneration represents the .metadata.generation 132 | that the condition was set based upon. For instance, if .metadata.generation 133 | is currently 12, but the .status.conditions[x].observedGeneration 134 | is 9, the condition is out of date with respect to the current 135 | state of the instance. 136 | format: int64 137 | minimum: 0 138 | type: integer 139 | reason: 140 | description: reason contains a programmatic identifier indicating 141 | the reason for the condition's last transition. Producers 142 | of specific condition types may define expected values and 143 | meanings for this field, and whether the values are considered 144 | a guaranteed API. The value should be a CamelCase string. 145 | This field may not be empty. 146 | maxLength: 1024 147 | minLength: 1 148 | pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ 149 | type: string 150 | status: 151 | description: status of the condition, one of True, False, Unknown. 152 | enum: 153 | - "True" 154 | - "False" 155 | - Unknown 156 | type: string 157 | type: 158 | description: type of condition in CamelCase or in foo.example.com/CamelCase. 159 | --- Many .condition.type values are consistent across resources 160 | like Available, but because arbitrary conditions can be useful 161 | (see .node.status.conditions), the ability to deconflict is 162 | important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) 163 | maxLength: 316 164 | 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])$ 165 | type: string 166 | required: 167 | - lastTransitionTime 168 | - message 169 | - reason 170 | - status 171 | - type 172 | type: object 173 | type: array 174 | descriptiveName: 175 | type: string 176 | publicKey: 177 | type: string 178 | replicas: 179 | type: integer 180 | reversion: 181 | type: integer 182 | version: 183 | type: string 184 | required: 185 | - availableReplicas 186 | - claims 187 | - conditions 188 | - publicKey 189 | - replicas 190 | type: object 191 | type: object 192 | served: true 193 | storage: true 194 | subresources: 195 | scale: 196 | specReplicasPath: .spec.replicas 197 | statusReplicasPath: .status.replicas 198 | status: {} 199 | -------------------------------------------------------------------------------- /deploy/crds/kasmcloud.io_kasmcloudhosts.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.12.0 7 | name: kasmcloudhosts.kasmcloud.io 8 | spec: 9 | group: kasmcloud.io 10 | names: 11 | kind: KasmCloudHost 12 | listKind: KasmCloudHostList 13 | plural: kasmcloudhosts 14 | singular: kasmcloudhost 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: KasmCloudHost is the Schema for the kasmcloudhosts API 21 | properties: 22 | apiVersion: 23 | description: 'APIVersion defines the versioned schema of this representation 24 | of an object. Servers should convert recognized schemas to the latest 25 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 26 | type: string 27 | kind: 28 | description: 'Kind is a string value representing the REST resource this 29 | object represents. Servers may infer this from the endpoint the client 30 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 31 | type: string 32 | metadata: 33 | type: object 34 | spec: 35 | description: KasmCloudHostSpec defines the desired state of KasmCloudHost 36 | properties: 37 | clusterIssuers: 38 | items: 39 | type: string 40 | type: array 41 | type: object 42 | status: 43 | description: KasmCloudHostStatus defines the observed state of KasmCloudHost 44 | properties: 45 | actors: 46 | properties: 47 | count: 48 | format: int64 49 | type: integer 50 | instanceCount: 51 | format: int64 52 | type: integer 53 | required: 54 | - count 55 | - instanceCount 56 | type: object 57 | clusterIssuers: 58 | items: 59 | type: string 60 | type: array 61 | clusterPublicKey: 62 | type: string 63 | instance: 64 | type: string 65 | kubeNodeName: 66 | type: string 67 | preInstance: 68 | type: string 69 | providers: 70 | properties: 71 | count: 72 | format: int64 73 | type: integer 74 | required: 75 | - count 76 | type: object 77 | publicKey: 78 | type: string 79 | required: 80 | - actors 81 | - clusterIssuers 82 | - clusterPublicKey 83 | - providers 84 | - publicKey 85 | type: object 86 | type: object 87 | served: true 88 | storage: true 89 | subresources: 90 | status: {} 91 | -------------------------------------------------------------------------------- /deploy/crds/kasmcloud.io_links.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.12.0 7 | name: links.kasmcloud.io 8 | spec: 9 | group: kasmcloud.io 10 | names: 11 | categories: 12 | - kasmcloud 13 | kind: Link 14 | listKind: LinkList 15 | plural: links 16 | singular: link 17 | scope: Namespaced 18 | versions: 19 | - additionalPrinterColumns: 20 | - jsonPath: .spec.contractId 21 | name: ContractId 22 | type: string 23 | - jsonPath: .status.linkName 24 | name: Link 25 | type: string 26 | - jsonPath: .status.actorKey 27 | name: ActoryKey 28 | type: string 29 | - jsonPath: .status.providerKey 30 | name: ProviderKey 31 | type: string 32 | name: v1alpha1 33 | schema: 34 | openAPIV3Schema: 35 | description: Link is the Schema for the links API 36 | properties: 37 | apiVersion: 38 | description: 'APIVersion defines the versioned schema of this representation 39 | of an object. Servers should convert recognized schemas to the latest 40 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 41 | type: string 42 | kind: 43 | description: 'Kind is a string value representing the REST resource this 44 | object represents. Servers may infer this from the endpoint the client 45 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 46 | type: string 47 | metadata: 48 | type: object 49 | spec: 50 | description: LinkSpec defines the desired state of Link 51 | properties: 52 | actor: 53 | properties: 54 | key: 55 | type: string 56 | name: 57 | type: string 58 | type: object 59 | contractId: 60 | minLength: 1 61 | type: string 62 | linkName: 63 | type: string 64 | provider: 65 | properties: 66 | key: 67 | type: string 68 | name: 69 | type: string 70 | type: object 71 | values: 72 | additionalProperties: 73 | type: string 74 | type: object 75 | required: 76 | - actor 77 | - contractId 78 | - provider 79 | type: object 80 | status: 81 | description: LinkStatus defines the observed state of Link 82 | properties: 83 | actorKey: 84 | type: string 85 | conditions: 86 | items: 87 | description: "Condition contains details for one aspect of the current 88 | state of this API Resource. --- This struct is intended for direct 89 | use as an array at the field path .status.conditions. For example, 90 | \n type FooStatus struct{ // Represents the observations of a 91 | foo's current state. // Known .status.conditions.type are: \"Available\", 92 | \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge 93 | // +listType=map // +listMapKey=type Conditions []metav1.Condition 94 | `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" 95 | protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" 96 | properties: 97 | lastTransitionTime: 98 | description: lastTransitionTime is the last time the condition 99 | transitioned from one status to another. This should be when 100 | the underlying condition changed. If that is not known, then 101 | using the time when the API field changed is acceptable. 102 | format: date-time 103 | type: string 104 | message: 105 | description: message is a human readable message indicating 106 | details about the transition. This may be an empty string. 107 | maxLength: 32768 108 | type: string 109 | observedGeneration: 110 | description: observedGeneration represents the .metadata.generation 111 | that the condition was set based upon. For instance, if .metadata.generation 112 | is currently 12, but the .status.conditions[x].observedGeneration 113 | is 9, the condition is out of date with respect to the current 114 | state of the instance. 115 | format: int64 116 | minimum: 0 117 | type: integer 118 | reason: 119 | description: reason contains a programmatic identifier indicating 120 | the reason for the condition's last transition. Producers 121 | of specific condition types may define expected values and 122 | meanings for this field, and whether the values are considered 123 | a guaranteed API. The value should be a CamelCase string. 124 | This field may not be empty. 125 | maxLength: 1024 126 | minLength: 1 127 | pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ 128 | type: string 129 | status: 130 | description: status of the condition, one of True, False, Unknown. 131 | enum: 132 | - "True" 133 | - "False" 134 | - Unknown 135 | type: string 136 | type: 137 | description: type of condition in CamelCase or in foo.example.com/CamelCase. 138 | --- Many .condition.type values are consistent across resources 139 | like Available, but because arbitrary conditions can be useful 140 | (see .node.status.conditions), the ability to deconflict is 141 | important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) 142 | maxLength: 316 143 | 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])$ 144 | type: string 145 | required: 146 | - lastTransitionTime 147 | - message 148 | - reason 149 | - status 150 | - type 151 | type: object 152 | type: array 153 | linkName: 154 | type: string 155 | providerKey: 156 | type: string 157 | required: 158 | - actorKey 159 | - conditions 160 | - linkName 161 | - providerKey 162 | type: object 163 | type: object 164 | served: true 165 | storage: true 166 | subresources: 167 | status: {} 168 | -------------------------------------------------------------------------------- /deploy/crds/kasmcloud.io_providers.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.12.0 7 | name: providers.kasmcloud.io 8 | spec: 9 | group: kasmcloud.io 10 | names: 11 | categories: 12 | - kasmcloud 13 | kind: Provider 14 | listKind: ProviderList 15 | plural: providers 16 | singular: provider 17 | scope: Namespaced 18 | versions: 19 | - additionalPrinterColumns: 20 | - jsonPath: .status.descriptiveName 21 | name: Desc 22 | type: string 23 | - jsonPath: .status.publicKey 24 | name: PublicKey 25 | type: string 26 | - jsonPath: .spec.link 27 | name: Link 28 | type: string 29 | - jsonPath: .status.contractId 30 | name: ContractId 31 | type: string 32 | - jsonPath: .spec.image 33 | name: Image 34 | type: string 35 | name: v1alpha1 36 | schema: 37 | openAPIV3Schema: 38 | description: Provider is the Schema for the providers API 39 | properties: 40 | apiVersion: 41 | description: 'APIVersion defines the versioned schema of this representation 42 | of an object. Servers should convert recognized schemas to the latest 43 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 44 | type: string 45 | kind: 46 | description: 'Kind is a string value representing the REST resource this 47 | object represents. Servers may infer this from the endpoint the client 48 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 49 | type: string 50 | metadata: 51 | type: object 52 | spec: 53 | description: ProviderSpec defines the desired state of Provider 54 | properties: 55 | host: 56 | minLength: 1 57 | type: string 58 | image: 59 | minLength: 1 60 | type: string 61 | link: 62 | minLength: 1 63 | type: string 64 | required: 65 | - host 66 | - image 67 | - link 68 | type: object 69 | status: 70 | description: ProviderStatus defines the observed state of Provider 71 | properties: 72 | architectureTargets: 73 | items: 74 | type: string 75 | type: array 76 | claims: 77 | properties: 78 | expires: 79 | format: date-time 80 | type: string 81 | issuedAt: 82 | format: date-time 83 | type: string 84 | issuer: 85 | type: string 86 | notBefore: 87 | format: date-time 88 | type: string 89 | subject: 90 | type: string 91 | required: 92 | - issuedAt 93 | - issuer 94 | - subject 95 | type: object 96 | conditions: 97 | items: 98 | description: "Condition contains details for one aspect of the current 99 | state of this API Resource. --- This struct is intended for direct 100 | use as an array at the field path .status.conditions. For example, 101 | \n type FooStatus struct{ // Represents the observations of a 102 | foo's current state. // Known .status.conditions.type are: \"Available\", 103 | \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge 104 | // +listType=map // +listMapKey=type Conditions []metav1.Condition 105 | `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" 106 | protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" 107 | properties: 108 | lastTransitionTime: 109 | description: lastTransitionTime is the last time the condition 110 | transitioned from one status to another. This should be when 111 | the underlying condition changed. If that is not known, then 112 | using the time when the API field changed is acceptable. 113 | format: date-time 114 | type: string 115 | message: 116 | description: message is a human readable message indicating 117 | details about the transition. This may be an empty string. 118 | maxLength: 32768 119 | type: string 120 | observedGeneration: 121 | description: observedGeneration represents the .metadata.generation 122 | that the condition was set based upon. For instance, if .metadata.generation 123 | is currently 12, but the .status.conditions[x].observedGeneration 124 | is 9, the condition is out of date with respect to the current 125 | state of the instance. 126 | format: int64 127 | minimum: 0 128 | type: integer 129 | reason: 130 | description: reason contains a programmatic identifier indicating 131 | the reason for the condition's last transition. Producers 132 | of specific condition types may define expected values and 133 | meanings for this field, and whether the values are considered 134 | a guaranteed API. The value should be a CamelCase string. 135 | This field may not be empty. 136 | maxLength: 1024 137 | minLength: 1 138 | pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ 139 | type: string 140 | status: 141 | description: status of the condition, one of True, False, Unknown. 142 | enum: 143 | - "True" 144 | - "False" 145 | - Unknown 146 | type: string 147 | type: 148 | description: type of condition in CamelCase or in foo.example.com/CamelCase. 149 | --- Many .condition.type values are consistent across resources 150 | like Available, but because arbitrary conditions can be useful 151 | (see .node.status.conditions), the ability to deconflict is 152 | important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) 153 | maxLength: 316 154 | 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])$ 155 | type: string 156 | required: 157 | - lastTransitionTime 158 | - message 159 | - reason 160 | - status 161 | - type 162 | type: object 163 | type: array 164 | contractId: 165 | type: string 166 | descriptiveName: 167 | type: string 168 | instanceId: 169 | type: string 170 | publicKey: 171 | type: string 172 | reversion: 173 | type: integer 174 | vendor: 175 | type: string 176 | version: 177 | type: string 178 | required: 179 | - architectureTargets 180 | - claims 181 | - conditions 182 | - contractId 183 | - instanceId 184 | - publicKey 185 | - vendor 186 | type: object 187 | type: object 188 | served: true 189 | storage: true 190 | subresources: 191 | status: {} 192 | -------------------------------------------------------------------------------- /deploy/kasmcloud_host_daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | labels: 5 | app: kasmcloud-host 6 | name: kasmcloud-host 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: kasmcloud-host 11 | kasmcloud-host/type: domain 12 | template: 13 | metadata: 14 | labels: 15 | app: kasmcloud-host 16 | kasmcloud-host/type: domain 17 | spec: 18 | containers: 19 | - command: 20 | - "/usr/local/bin/kasmcloud" 21 | image: ghcr.io/iceber/kasmcloud/host:v0.0.2 22 | name: host 23 | env: 24 | - name: KASMCLOUD_HOST_NAME 25 | valueFrom: 26 | fieldRef: 27 | fieldPath: spec.nodeName 28 | - name: KASMCLOUD_NATS_HOST 29 | value: "kasmcloud-nats.default" 30 | - name: KASMCLOUD_NATS_PORT 31 | value: "4222" 32 | - name: KUBERNETES_NODE_NAME 33 | valueFrom: 34 | fieldRef: 35 | fieldPath: spec.nodeName 36 | serviceAccountName: kasmcloud-host 37 | -------------------------------------------------------------------------------- /deploy/kasmcloud_host_default.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | labels: 5 | app: kasmcloud-host 6 | name: kasmcloud-host-default 7 | spec: 8 | containers: 9 | - command: 10 | - "/usr/local/bin/kasmcloud" 11 | image: ghcr.io/iceber/kasmcloud/host:v0.0.2 12 | name: host 13 | env: 14 | - name: KASMCLOUD_HOST_NAME 15 | value: "default" 16 | - name: KASMCLOUD_NATS_HOST 17 | value: "kasmcloud-nats.default" 18 | - name: KASMCLOUD_NATS_PORT 19 | value: "4222" 20 | - name: KUBERNETES_NODE_NAME 21 | valueFrom: 22 | fieldRef: 23 | fieldPath: spec.nodeName 24 | serviceAccountName: kasmcloud-host 25 | -------------------------------------------------------------------------------- /deploy/kasmcloud_host_deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: kasmcloud-host 6 | name: kasmcloud-temporary-host 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: kasmcloud-host 12 | kasmcloud-host/type: temporary 13 | template: 14 | metadata: 15 | labels: 16 | app: kasmcloud-host 17 | kasmcloud-host/type: temporary 18 | spec: 19 | containers: 20 | - command: 21 | - "/usr/local/bin/kasmcloud" 22 | image: ghcr.io/iceber/kasmcloud/host:v0.0.2 23 | name: host 24 | env: 25 | - name: KASMCLOUD_TEMPORARY 26 | value: true 27 | - name: KASMCLOUD_NATS_URL 28 | value: "nats://kasmcloud-nats.default:4222" 29 | - name: KUBERNETES_NODE_NAME 30 | valueFrom: 31 | fieldRef: 32 | fieldPath: spec.nodeName 33 | serviceAccountName: kasmcloud-host 34 | -------------------------------------------------------------------------------- /deploy/kasmcloud_host_rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: kasmcloud-host 5 | rules: 6 | - apiGroups: 7 | - 'kasmcloud.io' 8 | resources: 9 | - '*' 10 | verbs: 11 | - '*' 12 | --- 13 | apiVersion: rbac.authorization.k8s.io/v1 14 | kind: RoleBinding 15 | metadata: 16 | name: kasmcloud-host 17 | roleRef: 18 | apiGroup: rbac.authorization.k8s.io 19 | kind: Role 20 | name: kasmcloud-host 21 | subjects: 22 | - kind: ServiceAccount 23 | name: kasmcloud-host 24 | --- 25 | apiVersion: v1 26 | kind: ServiceAccount 27 | metadata: 28 | name: kasmcloud-host 29 | -------------------------------------------------------------------------------- /deploy/webhook/kasmcloud_webhook_manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: MutatingWebhookConfiguration 4 | metadata: 5 | name: mutating-webhook-configuration 6 | webhooks: 7 | - admissionReviewVersions: 8 | - v1 9 | clientConfig: 10 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 11 | service: 12 | name: kasmcloud-webhook 13 | namespace: kasmcloud-system 14 | path: /mutate-kasmcloud-io-v1alpha1-actor 15 | failurePolicy: Fail 16 | name: mactor.kb.io 17 | rules: 18 | - apiGroups: 19 | - kasmcloud.io 20 | apiVersions: 21 | - v1alpha1 22 | operations: 23 | - CREATE 24 | - UPDATE 25 | resources: 26 | - actors 27 | sideEffects: None 28 | - admissionReviewVersions: 29 | - v1 30 | clientConfig: 31 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 32 | service: 33 | name: kasmcloud-webhook 34 | namespace: kasmcloud-system 35 | path: /mutate-kasmcloud-io-v1alpha1-link 36 | failurePolicy: Fail 37 | name: mlink.kb.io 38 | rules: 39 | - apiGroups: 40 | - kasmcloud.io 41 | apiVersions: 42 | - v1alpha1 43 | operations: 44 | - CREATE 45 | - UPDATE 46 | resources: 47 | - links 48 | sideEffects: None 49 | - admissionReviewVersions: 50 | - v1 51 | clientConfig: 52 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 53 | service: 54 | name: kasmcloud-webhook 55 | namespace: kasmcloud-system 56 | path: /mutate-kasmcloud-io-v1alpha1-link 57 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 58 | service: 59 | name: kasmcloud-webhook 60 | namespace: kasmcloud-system 61 | path: /mutate-kasmcloud-io-v1alpha1-link 62 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 63 | service: 64 | name: kasmcloud-webhook 65 | namespace: kasmcloud-system 66 | path: /mutate-kasmcloud-io-v1alpha1-link 67 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 68 | service: 69 | name: kasmcloud-webhook 70 | namespace: kasmcloud-system 71 | path: /mutate-kasmcloud-io-v1alpha1-link 72 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 73 | service: 74 | name: kasmcloud-webhook 75 | namespace: kasmcloud-system 76 | path: /mutate-kasmcloud-io-v1alpha1-link 77 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 78 | service: 79 | name: kasmcloud-webhook 80 | namespace: kasmcloud-system 81 | path: /mutate-kasmcloud-io-v1alpha1-link 82 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 83 | service: 84 | name: kasmcloud-webhook 85 | namespace: kasmcloud-system 86 | path: /mutate-kasmcloud-io-v1alpha1-provider 87 | failurePolicy: Fail 88 | name: mprovider.kb.io 89 | rules: 90 | - apiGroups: 91 | - kasmcloud.io 92 | apiVersions: 93 | - v1alpha1 94 | operations: 95 | - CREATE 96 | - UPDATE 97 | resources: 98 | - providers 99 | sideEffects: None 100 | --- 101 | apiVersion: admissionregistration.k8s.io/v1 102 | kind: ValidatingWebhookConfiguration 103 | metadata: 104 | name: validating-webhook-configuration 105 | webhooks: 106 | - admissionReviewVersions: 107 | - v1 108 | clientConfig: 109 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 110 | service: 111 | name: kasmcloud-webhook 112 | namespace: kasmcloud-system 113 | path: /validate-kasmcloud-io-v1alpha1-actor 114 | failurePolicy: Fail 115 | name: vactor.kb.io 116 | rules: 117 | - apiGroups: 118 | - kasmcloud.io 119 | apiVersions: 120 | - v1alpha1 121 | operations: 122 | - CREATE 123 | - UPDATE 124 | resources: 125 | - actors 126 | sideEffects: None 127 | - admissionReviewVersions: 128 | - v1 129 | clientConfig: 130 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 131 | service: 132 | name: kasmcloud-webhook 133 | namespace: kasmcloud-system 134 | path: /validate-kasmcloud-io-v1alpha1-link 135 | failurePolicy: Fail 136 | name: vlink.kb.io 137 | rules: 138 | - apiGroups: 139 | - kasmcloud.io 140 | apiVersions: 141 | - v1alpha1 142 | operations: 143 | - CREATE 144 | - UPDATE 145 | resources: 146 | - links 147 | sideEffects: None 148 | - admissionReviewVersions: 149 | - v1 150 | clientConfig: 151 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 152 | service: 153 | name: kasmcloud-webhook 154 | namespace: kasmcloud-system 155 | path: /validate-kasmcloud-io-v1alpha1-provider 156 | failurePolicy: Fail 157 | name: vprovider.kb.io 158 | rules: 159 | - apiGroups: 160 | - kasmcloud.io 161 | apiVersions: 162 | - v1alpha1 163 | operations: 164 | - CREATE 165 | - UPDATE 166 | resources: 167 | - providers 168 | sideEffects: None 169 | -------------------------------------------------------------------------------- /deploy/webhook/kasmcloud_webhook_server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: kasmcloud-system 5 | --- 6 | apiVersion: v1 7 | kind: Service 8 | metadata: 9 | namespace: kasmcloud-system 10 | name: kasmcloud-webhook 11 | spec: 12 | ports: 13 | - port: 443 14 | protocol: TCP 15 | targetPort: 9443 16 | selector: 17 | app: kasmcloud-webhook 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: kasmcloud-webhook 23 | namespace: kasmcloud-system 24 | spec: 25 | replicas: 1 26 | selector: 27 | matchLabels: 28 | app: kasmcloud-webhook 29 | template: 30 | metadata: 31 | labels: 32 | app: kasmcloud-webhook 33 | spec: 34 | containers: 35 | - name: manager 36 | image: ghcr.io/iceber/kasmcloud/webhook:v0.0.2 37 | ports: 38 | - containerPort: 9443 39 | name: webhook-server 40 | protocol: TCP 41 | volumeMounts: 42 | - mountPath: /tmp/k8s-webhook-server/serving-certs 43 | name: cert 44 | readOnly: true 45 | volumes: 46 | - name: cert 47 | secret: 48 | defaultMode: 420 49 | secretName: kasmcloud-webhook-server-cert 50 | --- 51 | apiVersion: v1 52 | data: 53 | ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 54 | tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lSQUxQR1RvSnduRjd1MW1BWGx6R25rOHd3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNekV5TVRJd09EQTFNak5hRncweU5EQXpNVEV3T0RBMU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXK1Ewc05HQ28wdXkrWHFvc3UrSjJFSHZ3WjkrbG5CZEtHa0hnCjZwbzM5K3NGN0xqN0VkeWVwdUJyRnB1UStBSVhzYkFnT05qRnFaUmx0alRUczVnZituQ25zbThheGZTckc4MSsKdGhZdGF2UnRiR1R0cHFUQXcya2VoWkxnRmVsV250STZJUnowYlJpR2tTNWphY3FTajBYeUMyakd4S2MxOWJ1dgpxcVpSZEk1VXhvRjh0VWp5UkZLeWRBREY4Mzlrb1ZQbE9jNFk0UWZvZENSdFd2aExxRHh6Wll3ZEFFcjBja0w0CkNzdVh2c3RsblEzMmhacEcrdkVJbEk1eHdxdHVlcFRTZXF2N0RwNFpZeTRWYmFQMThzakpPblRpK1FzYXN6dHEKd0p3dzJ6TENyeFpoa0V0M294NWlOdnFlTGVlM0ZuUEI3Q3FLa2QyYzc5MndVN0NOQWdNQkFBR2pnWTB3Z1lvdwpEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3YWdZRFZSMFJBUUgvQkdBd1hvSW1hMkZ6CmJXTnNiM1ZrTFhkbFltaHZiMnN1YTJGemJXTnNiM1ZrTFhONWMzUmxiUzV6ZG1PQ05HdGhjMjFqYkc5MVpDMTMKWldKb2IyOXJMbXRoYzIxamJHOTFaQzF6ZVhOMFpXMHVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUNoNWgrVWxBay9NT0tESEZPRWxwUXV3K21SMHNrUUxoU29PWkRYc1dHTXp2TGZPCjg4NVBMcDdkY1o5SDZJbGs0UVhpNlBhQTQ5aDBLell0azZKVU5LMzlzbTdHQ0Y0eVhqM2Fqb3dNNjAvQWRwVjkKYkd2cHNkb1hWQTEyYWVGWng5WjlNdEZ6V1ZsVGh4STlwVU9TWmcxcy9OL2hGVTVvb1BUTVdyUDRuWnp6MXljWgpSWkVqRTVsTkdVaTNLMTJSSU0ybzZnOVhBMCt2YWhubUk4NXBNSDIyN3pmbE9rb29rV2xtZUFEWjdmdFJOclUyCjBHUmxpOVNONk9KdTlEdW91VTErZEpLSG1Nbys1SUl2TDBpeUkwU1ZadE5CM05qUmJVVkhqV3VPemZVVXdneWQKT0VJbDlYTXA3aWRLSGpmZUpxU0JVT05vRGM5bU1tTDRtNk5tZ0JjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 55 | tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBbHZrTkxEUmdxTkxzdmw2cUxMdmlkaEI3OEdmZnBad1hTaHBCNE9xYU4vZnJCZXk0Cit4SGNucWJnYXhhYmtQZ0NGN0d3SURqWXhhbVVaYlkwMDdPWUgvcHdwN0p2R3NYMHF4dk5mcllXTFdyMGJXeGsKN2Fha3dNTnBIb1dTNEJYcFZwN1NPaUVjOUcwWWhwRXVZMm5La285RjhndG94c1NuTmZXN3I2cW1VWFNPVk1hQgpmTFZJOGtSU3NuUUF4Zk4vWktGVDVUbk9HT0VINkhRa2JWcjRTNmc4YzJXTUhRQks5SEpDK0FyTGw3N0xaWjBOCjlvV2FSdnJ4Q0pTT2NjS3JibnFVMG5xcit3NmVHV011RlcyajlmTEl5VHAwNHZrTEdyTTdhc0NjTU5zeXdxOFcKWVpCTGQ2TWVZamI2bmkzbnR4Wnp3ZXdxaXBIZG5PL2RzRk93alFJREFRQUJBb0lCQUdUaC9YUnBkNkc0ZUd4cApjNHU2N1FQNTFlZjZjVThIOCtRWGthZ0svSXlqeE0vTHRIeEpIa1NCdkhxeXU3ZHVrbTlrMUExa0R5NWVPQVNUCmNjOTVOaEZBVjZFbnNlaVJHUUp4N0twY1lJdHU2ZkJvdXpTMjQyYlA3V0Z3M29tUStzQmdGcGl6WWRiUFE5RkEKR0dCajQvSjFmTWJWNnVqUHBmQi95VEFFZDJqNzFCY0NaeHhYaEFiMHgxQzNRak5TcWcvY0JFb1Z1MWxTMEVSMApIZFRRSnJET1RNVmpqMmo4S0xUSGRORnhHMXI1M2k3TzVYVHdZajAzVkxSaWx1ZFIrRUJyVldYSHZJMXhXSTJKCkZpMFRCOGVXUHlwNDIwR3JwZGJtQ3dDYmtuKzNqVkkvcUEvOUQzS1pHaGZXOW03ZUFVdEtGL3V5MFNXYXQ4MzAKQXR0TkFGMENnWUVBeER2TnczTUY5Q09YZDUweVp0QnlVY3FnNUR2NU9jMVZ4UCtEUHhFaVdTLzB2andmOTRBSAp1OUJ1ZC9qdkV0VjF2bnE4YmFSWWVLaWxWYXpiQ2h6a3I3ckFTNlZXczFXZ3VxVlpzTjFKM2V5UE1WRnhDOTBECnhlalVjM3ZhWXJSbCtVcWdPYW5RQ1o2MHdNU0QyU1E1M3hYaS85YU5IWUFNOVNLblFUYjJwUjhDZ1lFQXhQUk0KZmlQaHJ3L1prVjIzU3dQM3RYUENQN25YbVpkSkdqRjBWeDRkNHVJZ2gzaXdXK09xVVFUYk8ydlpvYkQrRmFFagp1TEZQVTlqUkwyRGxlWXA1QitCWUo4cDg3RStPaCtNZWJKbGRLdGVFOUZXR2E1a2ZyQUZuaEpmTkZtWVFFNCtXClNZeUcvWWZvSDlKVys5SEp0Y1B5Q2J2YVY5aUlsU2s3L0ZScmFOTUNnWUIrZDhCenoyU3Q1R1pRNjVtNUUyd3AKOUhwZXZad3d1WG9tZlRvYjRqSU40czlMN3puMjY2ZVFUcE56ZEphM3Y2U2p2enhET1dEZldtazczeXhIeHpsSApYQWRrZVVsNFNKNU96QlFTM1hzbXNMZlVaQldFUE11YXVDUjd6UDJCRVp5SzNxS0dwejl4Zzd3Z1pFWkpJb1M5CmxzQjJlOUwwaVR0UW5TWEg3T0ZtcHdLQmdIV1lUOXU0M05WRHAybnVCTFNiaDlrbFpzUFoyR3J5TTBtYVlaWnEKZXZtdWZqR2swOWZwbFJXT3lyOHQ0K0Z3NWlSUDlCNGdiL2JtSy9BWDhLdWpEQ1N4eExEdGE2MUV5eHREZGpUdApWUDF4ZFFiVjBjNkl2S3BjcEV3OHZzMHkzMEp1cHhMKy90Sjl4UkRLNi9pckI3NnBZZWlCNnhvc1JWQ2FNQ0MyCjN2TFZBb0dBS2ZsVUQzV1h5ZjErYndyTDdTWHEwS1lrd3RPZHdWSEpjZzkwYitFejBuV1N2SEFMMUdwdUpsRk0KYm1IcWpOR1VKYjZmcXc1L1FydnB6azloWGxqV1BBUXlkeURUUkZyblc1ODVadHlSRW5tYjBhYzVHY2NjazBDYgpLSENvbWd2WWFVVkNlajVldXNhb3UyNEFtUDlKVEZENDVTNjVSczBsaWZvQUQ2a0lJbGc9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== 56 | kind: Secret 57 | metadata: 58 | name: kasmcloud-webhook-server-cert 59 | namespace: kasmcloud-system 60 | type: kubernetes.io/tls 61 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wasmcloud/kasmcloud 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/onsi/ginkgo/v2 v2.9.5 7 | github.com/onsi/gomega v1.27.7 8 | k8s.io/api v0.27.2 9 | k8s.io/apimachinery v0.27.2 10 | k8s.io/client-go v0.27.2 11 | sigs.k8s.io/controller-runtime v0.15.0 12 | ) 13 | 14 | require ( 15 | github.com/beorn7/perks v1.0.1 // indirect 16 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 19 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 20 | github.com/fsnotify/fsnotify v1.6.0 // indirect 21 | github.com/go-logr/logr v1.2.4 // indirect 22 | github.com/go-logr/zapr v1.2.4 // indirect 23 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 24 | github.com/go-openapi/jsonreference v0.20.1 // indirect 25 | github.com/go-openapi/swag v0.22.3 // indirect 26 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 27 | github.com/gogo/protobuf v1.3.2 // indirect 28 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 29 | github.com/golang/protobuf v1.5.3 // indirect 30 | github.com/google/gnostic v0.5.7-v3refs // indirect 31 | github.com/google/go-cmp v0.5.9 // indirect 32 | github.com/google/gofuzz v1.1.0 // indirect 33 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect 34 | github.com/google/uuid v1.3.0 // indirect 35 | github.com/imdario/mergo v0.3.6 // indirect 36 | github.com/josharian/intern v1.0.0 // indirect 37 | github.com/json-iterator/go v1.1.12 // indirect 38 | github.com/mailru/easyjson v0.7.7 // indirect 39 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 41 | github.com/modern-go/reflect2 v1.0.2 // indirect 42 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 43 | github.com/pkg/errors v0.9.1 // indirect 44 | github.com/prometheus/client_golang v1.15.1 // indirect 45 | github.com/prometheus/client_model v0.4.0 // indirect 46 | github.com/prometheus/common v0.42.0 // indirect 47 | github.com/prometheus/procfs v0.9.0 // indirect 48 | github.com/spf13/pflag v1.0.5 // indirect 49 | go.uber.org/atomic v1.7.0 // indirect 50 | go.uber.org/multierr v1.6.0 // indirect 51 | go.uber.org/zap v1.24.0 // indirect 52 | golang.org/x/net v0.10.0 // indirect 53 | golang.org/x/oauth2 v0.5.0 // indirect 54 | golang.org/x/sys v0.8.0 // indirect 55 | golang.org/x/term v0.8.0 // indirect 56 | golang.org/x/text v0.9.0 // indirect 57 | golang.org/x/time v0.3.0 // indirect 58 | golang.org/x/tools v0.9.1 // indirect 59 | gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect 60 | google.golang.org/appengine v1.6.7 // indirect 61 | google.golang.org/protobuf v1.30.0 // indirect 62 | gopkg.in/inf.v0 v0.9.1 // indirect 63 | gopkg.in/yaml.v2 v2.4.0 // indirect 64 | gopkg.in/yaml.v3 v3.0.1 // indirect 65 | k8s.io/apiextensions-apiserver v0.27.2 // indirect 66 | k8s.io/component-base v0.27.2 // indirect 67 | k8s.io/klog/v2 v2.90.1 // indirect 68 | k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect 69 | k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect 70 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 71 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 72 | sigs.k8s.io/yaml v1.3.0 // indirect 73 | ) 74 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 4 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 5 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 6 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 7 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 8 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 9 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 10 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 11 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 12 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 13 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 14 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 19 | github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= 20 | github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 21 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 22 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 23 | github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= 24 | github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= 25 | github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= 26 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 27 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 28 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 29 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 30 | github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 31 | github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= 32 | github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= 33 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 34 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 35 | github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= 36 | github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 37 | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= 38 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 39 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 40 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 41 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 42 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 43 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 44 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 45 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 46 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 47 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 48 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 49 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 50 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 51 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 52 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 53 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 54 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 55 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 56 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 57 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 58 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 59 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 60 | github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= 61 | github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= 62 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 63 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 64 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 65 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 66 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 67 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 68 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 69 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 70 | github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= 71 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 72 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= 73 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 74 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 75 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 76 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 77 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 78 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 79 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 80 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 81 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 82 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 83 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 84 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 85 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 86 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 87 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 88 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 89 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 90 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 91 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 92 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 93 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 94 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 95 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 96 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 97 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 98 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 99 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 100 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 101 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 102 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 103 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 104 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 105 | github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= 106 | github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= 107 | github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= 108 | github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= 109 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 110 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 111 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 112 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 113 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 114 | github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= 115 | github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= 116 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 117 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= 118 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 119 | github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= 120 | github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= 121 | github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= 122 | github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= 123 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 124 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 125 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 126 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 127 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 128 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 129 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 130 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 131 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 132 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 133 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 134 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 135 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 136 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 137 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 138 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 139 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 140 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 141 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 142 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 143 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 144 | go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= 145 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 146 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 147 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= 148 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= 149 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 150 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 151 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 152 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 153 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 154 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 155 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 156 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 157 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 158 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 159 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 160 | golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= 161 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 162 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 163 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 164 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 165 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 166 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 167 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 168 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 169 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 170 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 171 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 172 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 173 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 174 | golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= 175 | golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= 176 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 177 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 178 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 179 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 180 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 181 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 182 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 183 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 184 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 185 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 186 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 187 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 188 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 189 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 190 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 191 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 192 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 193 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 194 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 195 | golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= 196 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 197 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 198 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 199 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 200 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 201 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 202 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 203 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 204 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 205 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 206 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 207 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 208 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 209 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 210 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 211 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 212 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 213 | golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= 214 | golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= 215 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 216 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 217 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 218 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 219 | gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= 220 | gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 221 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 222 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 223 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 224 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 225 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 226 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 227 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 228 | google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 229 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 230 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 231 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 232 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 233 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 234 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 235 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 236 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 237 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 238 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 239 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 240 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 241 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 242 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 243 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 244 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 245 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 246 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 247 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 248 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 249 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 250 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 251 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 252 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 253 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 254 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 255 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 256 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 257 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 258 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 259 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 260 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 261 | k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= 262 | k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= 263 | k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo= 264 | k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ= 265 | k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= 266 | k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= 267 | k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE= 268 | k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ= 269 | k8s.io/component-base v0.27.2 h1:neju+7s/r5O4x4/txeUONNTS9r1HsPbyoPBAtHsDCpo= 270 | k8s.io/component-base v0.27.2/go.mod h1:5UPk7EjfgrfgRIuDBFtsEFAe4DAvP3U+M8RTzoSJkpo= 271 | k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= 272 | k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 273 | k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= 274 | k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= 275 | k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= 276 | k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 277 | sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= 278 | sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= 279 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 280 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 281 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= 282 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= 283 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 284 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 285 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /sample.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kasmcloud.io/v1alpha1 2 | kind: Provider 3 | metadata: 4 | name: httpserver-default 5 | spec: 6 | host: default 7 | link: test 8 | # Image pull is faster, you can use `wasmcloud.azurecr.io/httpserver:0.17.0` 9 | image: ghcr.io/iceber/wasmcloud/httpserver:0.17.0-index 10 | --- 11 | apiVersion: kasmcloud.io/v1alpha1 12 | kind: Actor 13 | metadata: 14 | name: echo-default 15 | spec: 16 | host: default 17 | image: wasmcloud.azurecr.io/echo:0.3.8 18 | replicas: 10 19 | --- 20 | apiVersion: kasmcloud.io/v1alpha1 21 | kind: Link 22 | metadata: 23 | name: httpserver-echo 24 | spec: 25 | contractId: "wasmcloud:httpserver" 26 | actor: 27 | name: echo-default 28 | provider: 29 | name: httpserver-default 30 | values: 31 | address: "0.0.0.0:8080" 32 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use core::time::Duration; 2 | use std::collections::{hash_map, HashMap, HashSet}; 3 | use std::path::Path; 4 | use std::str::FromStr; 5 | use std::sync::Arc; 6 | 7 | use anyhow::Context as _; 8 | use chrono::{DateTime, LocalResult, TimeZone, Utc}; 9 | use clap::Parser; 10 | use config::{Config, Environment, File}; 11 | use futures::{future::ready, join, StreamExt}; 12 | use kube::error::{Error as KubeError, ErrorResponse}; 13 | use nkeys::KeyPair; 14 | use serde_derive::Deserialize; 15 | use thiserror::Error; 16 | use tokio::sync::RwLock; 17 | use tracing::{error, info, instrument, warn, Level as TracingLogLevel}; 18 | use url::Url; 19 | 20 | use k8s_openapi::apimachinery::pkg::apis::meta::v1 as metav1; 21 | use kube::api::{Patch, PatchParams}; 22 | use kube::runtime::controller::Action; 23 | use kube::runtime::watcher::Config as WatchConfig; 24 | use kube::runtime::{finalizer, Controller}; 25 | use kube::{api::PostParams, Api, Client, ResourceExt}; 26 | use wasmcloud_control_interface::LinkDefinition; 27 | use wasmcloud_core::logging::Level as WasmcloudLogLevel; 28 | use wasmcloud_core::OtelConfig; 29 | use wasmcloud_tracing::configure_tracing; 30 | 31 | use kasmcloud_apis::v1alpha1; 32 | use kasmcloud_host::*; 33 | 34 | lazy_static::lazy_static! { 35 | static ref KUBERNETES_NODE_NAME: String = std::env::var("KUBERNETES_NODE_NAME").unwrap(); 36 | } 37 | 38 | #[derive(Debug, Parser)] 39 | #[allow(clippy::struct_excessive_bools)] 40 | #[command(version, about, long_about = None)] 41 | struct Args { 42 | #[clap(long = "config", default_value = "/etc/kasmcloud/config.yaml")] 43 | config: String, 44 | } 45 | 46 | #[derive(Debug, Default, Deserialize)] 47 | struct KasmCloudConfig { 48 | temporary: bool, 49 | host_name: Option, 50 | 51 | nats_host: String, 52 | nats_port: u16, 53 | nats_jwt: Option, 54 | nats_seed: Option, 55 | js_domain: Option, 56 | 57 | // lattice_prefix: String, 58 | host_seed: Option, 59 | cluster_seed: Option, 60 | cluster_issuers: Option>, 61 | 62 | log_level: String, 63 | enable_structured_logging: bool, 64 | otel_traces_exporter: Option, 65 | otel_exporter_otlp_endpoint: Option, 66 | } 67 | 68 | impl KasmCloudConfig { 69 | fn get_hostname(self: &KasmCloudConfig) -> anyhow::Result { 70 | if let Some(host_name) = &self.host_name { 71 | Ok(host_name.clone()) 72 | } else if !self.temporary { 73 | Err(anyhow::Error::msg("require host name")) 74 | } else { 75 | let mut generator = names::Generator::default(); 76 | generator.next().context("generated failed") 77 | } 78 | } 79 | } 80 | 81 | #[tokio::main] 82 | async fn main() -> anyhow::Result<()> { 83 | let args: Args = Args::parse(); 84 | 85 | let mut builder = Config::builder() 86 | .set_default("nats_host", "127.0.0.1")? 87 | .set_default("nats_port", "4222")? 88 | .set_default("log_level", "info")? 89 | .set_default("enable_structured_logging", false)? 90 | .set_default("temporary", false)? 91 | .add_source(Environment::with_prefix("kasmcloud")); 92 | if Path::new(&args.config).exists() { 93 | builder = builder.add_source(File::with_name(&args.config)) 94 | } 95 | let config: KasmCloudConfig = builder.build()?.try_deserialize()?; 96 | 97 | let hostname = config.get_hostname()?; 98 | 99 | let otel_config = OtelConfig { 100 | traces_exporter: config.otel_traces_exporter, 101 | exporter_otlp_endpoint: config.otel_exporter_otlp_endpoint, 102 | }; 103 | let level = TracingLogLevel::from_str(&config.log_level).context("invalid log_level")?; 104 | let log_level = WasmcloudLogLevel::from(level); 105 | if let Err(e) = configure_tracing( 106 | "KasmCloud Host".to_string(), 107 | &otel_config, 108 | config.enable_structured_logging, 109 | Some(&log_level), 110 | ) { 111 | eprintln!("Failed to configure tracing: {e}"); 112 | }; 113 | 114 | let host_key = config 115 | .host_seed 116 | .as_deref() 117 | .map(KeyPair::from_seed) 118 | .transpose() 119 | .context("failed to contruct host key pair from seed")? 120 | .map(Arc::new); 121 | let cluster_key = config 122 | .cluster_seed 123 | .as_deref() 124 | .map(KeyPair::from_seed) 125 | .transpose() 126 | .context("failed to contruct cluster key pair from seed")? 127 | .map(Arc::new); 128 | let nats_key = config 129 | .nats_seed 130 | .as_deref() 131 | .map(KeyPair::from_seed) 132 | .transpose() 133 | .context("failed to contruct NATS key pair from seed")? 134 | .map(Arc::new); 135 | 136 | let rpc_config = NatsConfig { 137 | url: Url::parse(&format!("nats://{}:{}", config.nats_host, config.nats_port,)) 138 | .context("failed to parse nats")?, 139 | jwt: config.nats_jwt, 140 | key: nats_key, 141 | tls: false, 142 | timeout: Some(Duration::from_secs(2)), 143 | }; 144 | let prov_rpc_config = NatsConfig { 145 | timeout: None, 146 | ..rpc_config.clone() 147 | }; 148 | 149 | let namespace = "default".to_string(); 150 | let host_config = HostConfig { 151 | rpc_config, 152 | prov_rpc_config, 153 | js_domain: config.js_domain, 154 | 155 | host_key, 156 | lattice_prefix: namespace.clone(), 157 | 158 | cluster_key, 159 | cluster_issuers: config.cluster_issuers, 160 | 161 | log_level: Some(log_level), 162 | allow_file_load: true, 163 | }; 164 | let host = Host::new(hostname.clone(), host_config) 165 | .await 166 | .context("failed new actor handler")?; 167 | 168 | let client = Client::try_default().await?; 169 | 170 | let kasmcloud_host = serde_json::from_value(serde_json::json!({ 171 | "metadata": { 172 | "name": hostname, 173 | }, 174 | "spec": {} 175 | }))?; 176 | let host_client: Api = Api::namespaced(client.clone(), &namespace); 177 | if let Err(error) = host_client 178 | .create(&PostParams::default(), &kasmcloud_host) 179 | .await 180 | { 181 | if let KubeError::Api(ErrorResponse { reason, .. }) = &error { 182 | if reason != "AlreadyExists" { 183 | return Err(anyhow::anyhow!( 184 | "failed to create KasmCloudHost: {:?}", 185 | error 186 | )); 187 | } 188 | } else { 189 | return Err(anyhow::anyhow!("failed to create KasmCloudHost: {}", error)); 190 | } 191 | } 192 | update_host_status(&host_client, &host).await?; 193 | 194 | let actors: Api = Api::namespaced(client.clone(), &namespace); 195 | let providers: Api = Api::namespaced(client.clone(), &namespace); 196 | let links: Api = Api::namespaced(client.clone(), &namespace); 197 | 198 | let ctx = Arc::new(Ctx { 199 | host_client, 200 | actor_client: actors.clone(), 201 | provider_client: providers.clone(), 202 | link_client: links.clone(), 203 | host, 204 | links: RwLock::default(), 205 | }); 206 | 207 | let links = Controller::new(links, WatchConfig::default()) 208 | .run( 209 | |link, ctx| async move { 210 | let links = ctx.link_client.clone(); 211 | finalizer::finalizer(&links, "kasmcloud-host/cleanup", link, |event| async { 212 | match event { 213 | finalizer::Event::Apply(link) => add_link(link, ctx).await, 214 | finalizer::Event::Cleanup(link) => delete_link(link, ctx).await, 215 | } 216 | }) 217 | .await 218 | }, 219 | |_obj, _err, _| Action::requeue(Duration::from_secs(2)), 220 | Arc::clone(&ctx), 221 | ) 222 | .for_each(|_| ready(())); 223 | 224 | let actors = Controller::new(actors, WatchConfig::default()) 225 | .run( 226 | |actor, ctx| async move { 227 | if actor.spec.host != ctx.host.name { 228 | return Ok(Action::await_change()); 229 | } 230 | 231 | let actors = ctx.actor_client.clone(); 232 | finalizer::finalizer(&actors, "kasmcloud-host/cleanup", actor, |event| async { 233 | let result = match event { 234 | finalizer::Event::Apply(actor) => reconcile_actor(actor, ctx.clone()).await, 235 | finalizer::Event::Cleanup(actor) => delete_actor(actor, ctx.clone()).await, 236 | }; 237 | if let Err(_) = update_host_status(&ctx.host_client, &ctx.host).await {} 238 | result 239 | }) 240 | .await 241 | }, 242 | |_obj, _err, _| Action::requeue(Duration::from_secs(2)), 243 | Arc::clone(&ctx), 244 | ) 245 | .for_each(|_| ready(())); 246 | 247 | let providers = Controller::new(providers, WatchConfig::default()) 248 | .run( 249 | |product, ctx| async move { 250 | if product.spec.host != ctx.host.name { 251 | return Ok(Action::await_change()); 252 | } 253 | 254 | let providers = ctx.provider_client.clone(); 255 | finalizer::finalizer( 256 | &providers, 257 | "kasmcloud-host/cleanup", 258 | product, 259 | |event| async { 260 | match event { 261 | finalizer::Event::Apply(product) => { 262 | reconcile_provider(product, ctx).await 263 | } 264 | finalizer::Event::Cleanup(product) => { 265 | delete_provider(product, ctx).await 266 | } 267 | } 268 | }, 269 | ) 270 | .await 271 | }, 272 | |_obj, _err, _| Action::requeue(Duration::from_secs(2)), 273 | Arc::clone(&ctx), 274 | ) 275 | .for_each(|_| ready(())); 276 | 277 | join!(actors, providers, links); 278 | 279 | Ok(()) 280 | } 281 | 282 | #[instrument(skip(host_client, host))] 283 | async fn update_host_status( 284 | host_client: &Api, 285 | host: &Host, 286 | ) -> anyhow::Result<()> { 287 | let host_stats = host.stats().await; 288 | let status = v1alpha1::KasmCloudHostStatus { 289 | kube_node_name: KUBERNETES_NODE_NAME.clone(), 290 | public_key: host.host_key.public_key(), 291 | cluster_public_key: host.cluster_key.public_key(), 292 | providers: v1alpha1::HostProviderStatus { 293 | count: host_stats.provider_count, 294 | }, 295 | actors: v1alpha1::HostActorStatus { 296 | count: host_stats.actor_count, 297 | instance_count: host_stats.actor_instance_count, 298 | }, 299 | cluster_issuers: host.cluster_issuers.clone(), 300 | instance: String::default(), 301 | pre_instance: String::default(), 302 | }; 303 | let data = serde_json::json!({ "status": status }); 304 | host_client 305 | .patch_status(&host.name, &PatchParams::default(), &Patch::Merge(&data)) 306 | .await?; 307 | Ok(()) 308 | } 309 | 310 | // TODO(Iceber): add reflector store 311 | struct Ctx { 312 | actor_client: Api, 313 | provider_client: Api, 314 | link_client: Api, 315 | host_client: Api, 316 | host: Host, 317 | 318 | links: RwLock)>>, 319 | } 320 | 321 | #[derive(Debug, Error)] 322 | enum Error {} 323 | 324 | #[instrument(skip(actor, ctx))] 325 | async fn reconcile_actor(actor: Arc, ctx: Arc) -> Result { 326 | match ctx 327 | .host 328 | .reconcile_actor( 329 | actor.name_any(), 330 | actor.spec.image.clone(), 331 | actor.spec.replicas as usize, 332 | ) 333 | .await 334 | { 335 | Ok(claims) => { 336 | let public_key = claims.subject.clone(); 337 | let issued_at = 338 | if let LocalResult::Single(dt) = Utc.timestamp_opt(claims.issued_at as i64, 0) { 339 | dt 340 | } else { 341 | DateTime::::MIN_UTC 342 | }; 343 | let mut c = v1alpha1::Claims { 344 | issuer: claims.issuer.clone(), 345 | subject: claims.subject.clone(), 346 | issued_at: metav1::Time(issued_at), 347 | not_before: None, 348 | expires: None, 349 | }; 350 | if let Some(t) = claims.not_before { 351 | c.not_before = Some(metav1::Time( 352 | if let LocalResult::Single(dt) = Utc.timestamp_opt(t as i64, 0) { 353 | dt 354 | } else { 355 | DateTime::::MIN_UTC 356 | }, 357 | )) 358 | } 359 | if let Some(t) = claims.expires { 360 | c.expires = Some(metav1::Time( 361 | if let LocalResult::Single(dt) = Utc.timestamp_opt(t as i64, 0) { 362 | dt 363 | } else { 364 | DateTime::::MIN_UTC 365 | }, 366 | )) 367 | } 368 | 369 | let mut status = v1alpha1::ActorStatus { 370 | public_key, 371 | claims: c, 372 | descriptive_name: None, 373 | caps: None, 374 | capability_provider: None, 375 | call_alias: None, 376 | version: None, 377 | reversion: None, 378 | conditions: Vec::new(), 379 | replicas: actor.spec.replicas, 380 | available_replicas: actor.spec.replicas, 381 | }; 382 | if let Some(meta) = claims.metadata { 383 | status.descriptive_name = meta.name; 384 | if meta.provider { 385 | status.caps = meta.caps; 386 | } else { 387 | status.capability_provider = meta.caps; 388 | } 389 | status.reversion = meta.rev; 390 | status.version = meta.ver; 391 | status.call_alias = meta.call_alias; 392 | } 393 | 394 | let data = serde_json::json!({ "status": status }); 395 | if let Some(s) = actor.status.clone() { 396 | if s == status { 397 | return Ok(Action::await_change()); 398 | } 399 | } 400 | 401 | if let Err(err) = ctx 402 | .actor_client 403 | .patch_status( 404 | actor.name_any().as_str(), 405 | &PatchParams::default(), 406 | &Patch::Merge(&data), 407 | ) 408 | .await 409 | { 410 | error!("patch actor failed: {:?}", err); 411 | } 412 | } 413 | Err(err) => { 414 | error!("reconcile actor failed: {:?}", err); 415 | } 416 | } 417 | 418 | Ok(Action::await_change()) 419 | } 420 | 421 | #[instrument(skip(actor, ctx))] 422 | async fn delete_actor(actor: Arc, ctx: Arc) -> Result { 423 | if let Err(err) = ctx.host.remove_actor(actor.name_any()).await { 424 | error!("delete actor failed: {:?}", err); 425 | Ok(Action::requeue(Duration::from_secs(60))) 426 | } else { 427 | Ok(Action::await_change()) 428 | } 429 | } 430 | 431 | #[instrument(skip(link, ctx))] 432 | async fn add_link(link: Arc, ctx: Arc) -> Result { 433 | let spec = &link.spec; 434 | 435 | let mut ld = LinkDefinition::default(); 436 | ld.contract_id = spec.contract_id.clone(); 437 | ld.values = spec.values.clone(); 438 | if !spec.link_name.is_empty() { 439 | ld.link_name = spec.link_name.clone(); 440 | } else if !spec.provider.key.is_empty() { 441 | error!("please set provider's link name"); 442 | return Ok(Action::await_change()); 443 | } 444 | 445 | if let Some(status) = &link.status { 446 | if !status.provider_key.is_empty() 447 | && !status.actor_key.is_empty() 448 | && !status.link_name.is_empty() 449 | { 450 | ld.actor_id = status.actor_key.clone(); 451 | ld.provider_id = status.provider_key.clone(); 452 | ld.link_name = status.link_name.clone(); 453 | } 454 | } 455 | if ld.actor_id.is_empty() { 456 | let key = &spec.actor.key; 457 | let name = &spec.actor.name; 458 | if !key.is_empty() { 459 | ld.actor_id = key.clone(); 460 | } else if !name.is_empty() { 461 | match ctx.actor_client.get(name).await { 462 | Ok(actor) => { 463 | if let Some(status) = actor.status { 464 | if status.public_key == "" { 465 | warn!("actor:{name} public key is empty"); 466 | return Ok(Action::requeue(Duration::from_secs(10))); 467 | } else { 468 | ld.actor_id = status.public_key 469 | } 470 | } else { 471 | warn!("actor:{name} status is None"); 472 | return Ok(Action::requeue(Duration::from_secs(10))); 473 | } 474 | } 475 | Err(err) => { 476 | warn!("get actor:{name} failed: {:?}", err); 477 | return Ok(Action::requeue(Duration::from_secs(60))); 478 | } 479 | } 480 | } else { 481 | error!("link({})'s actor is empty", link.name_any()); 482 | return Ok(Action::await_change()); 483 | }; 484 | } 485 | if ld.provider_id.is_empty() || ld.link_name.is_empty() { 486 | let key = &spec.provider.key; 487 | let name = &spec.provider.name; 488 | if !name.is_empty() { 489 | match ctx.provider_client.get(name).await { 490 | Ok(provider) => { 491 | if !ld.link_name.is_empty() && ld.link_name != provider.spec.link { 492 | // TODO(Iceber): update conditions 493 | error!( 494 | "link name({}) not match provider's link({})", 495 | ld.link_name, provider.spec.link 496 | ); 497 | return Ok(Action::await_change()); 498 | } 499 | 500 | if let Some(status) = provider.status { 501 | if status.public_key.is_empty() { 502 | info!("provider:{name} public key is empty"); 503 | return Ok(Action::requeue(Duration::from_secs(10))); 504 | } else if !key.is_empty() && status.public_key != key.clone() { 505 | info!("provider:{name} public key is not match provider.key"); 506 | return Ok(Action::requeue(Duration::from_secs(10))); 507 | } else { 508 | ld.provider_id = status.public_key; 509 | ld.link_name = provider.spec.link; 510 | } 511 | } else { 512 | info!("provider:{name} status is None"); 513 | return Ok(Action::requeue(Duration::from_secs(10))); 514 | } 515 | } 516 | Err(err) => { 517 | warn!("get provider:{name} failed: {:?}", err); 518 | return Ok(Action::requeue(Duration::from_secs(60))); 519 | } 520 | } 521 | } else if !key.is_empty() { 522 | ld.provider_id = key.clone(); 523 | } else { 524 | error!("link({})'s provider is empty", link.name_any()); 525 | return Ok(Action::await_change()); 526 | }; 527 | } 528 | 529 | let id = format!("{}.{}.{}", ld.contract_id, ld.actor_id, ld.link_name); 530 | match ctx.links.write().await.entry(id.to_string()) { 531 | hash_map::Entry::Vacant(entry) => { 532 | if let Err(err) = ctx.host.add_linkdef(ld.clone()).await { 533 | error!("add linkdef failed: {:?}", err); 534 | // TODO(Iceber): update condition 535 | return Ok(Action::await_change()); 536 | } 537 | entry.insert((ld.clone(), HashSet::from([link.name_any()]))); 538 | } 539 | hash_map::Entry::Occupied(mut entry) => { 540 | // TODO(iceber): 541 | // 1. if the provider_id has changed, then you need to error or 542 | // otherwise handle the change. 543 | // 2. if ld.values is not equal group.link.values, return error. 544 | let (_, ref_names) = entry.get_mut(); 545 | ref_names.insert(link.name_any()); 546 | } 547 | } 548 | 549 | let status = v1alpha1::LinkStatus { 550 | provider_key: ld.provider_id.clone(), 551 | actor_key: ld.actor_id.clone(), 552 | link_name: ld.link_name.clone(), 553 | conditions: Vec::new(), 554 | }; 555 | let data = serde_json::json!({ "status": status }); 556 | 557 | if let Some(s) = link.status.clone() { 558 | if s == status { 559 | return Ok(Action::await_change()); 560 | } 561 | } 562 | 563 | if let Err(err) = ctx 564 | .link_client 565 | .patch_status( 566 | link.name_any().as_str(), 567 | &PatchParams::default(), 568 | &Patch::Merge(&data), 569 | ) 570 | .await 571 | { 572 | error!("patch link failed: {:?}", err); 573 | } 574 | Ok(Action::await_change()) 575 | } 576 | 577 | async fn delete_link(link: Arc, ctx: Arc) -> Result { 578 | if let Some(status) = &link.status { 579 | if !status.provider_key.is_empty() 580 | && !status.actor_key.is_empty() 581 | && !status.link_name.is_empty() 582 | { 583 | let id = format!( 584 | "{}.{}.{}", 585 | link.spec.contract_id, status.actor_key, status.link_name 586 | ); 587 | if let hash_map::Entry::Occupied(mut entry) = ctx.links.write().await.entry(id.clone()) 588 | { 589 | let (ld, ref_names) = entry.get_mut(); 590 | ref_names.remove(&link.name_any()); 591 | if ref_names.is_empty() { 592 | if let Err(err) = ctx.host.delete_linkdef(ld.clone()).await { 593 | error!("delete linkdef failed: {:?}", err); 594 | return Ok(Action::requeue(Duration::from_secs(10))); 595 | } 596 | entry.remove(); 597 | } 598 | } 599 | } 600 | } 601 | Ok(Action::await_change()) 602 | } 603 | 604 | async fn reconcile_provider(g: Arc, ctx: Arc) -> Result { 605 | let spec = g.spec.clone(); 606 | match ctx 607 | .host 608 | .start_provider(spec.link.clone(), spec.image.clone()) 609 | .await 610 | { 611 | Ok(claims) => { 612 | let public_key = claims.subject.clone(); 613 | let issued_at = 614 | if let LocalResult::Single(dt) = Utc.timestamp_opt(claims.issued_at as i64, 0) { 615 | dt 616 | } else { 617 | DateTime::::MIN_UTC 618 | }; 619 | let mut c = v1alpha1::Claims { 620 | issuer: claims.issuer.clone(), 621 | subject: claims.subject.clone(), 622 | issued_at: metav1::Time(issued_at), 623 | not_before: None, 624 | expires: None, 625 | }; 626 | if let Some(t) = claims.not_before { 627 | c.not_before = Some(metav1::Time( 628 | if let LocalResult::Single(dt) = Utc.timestamp_opt(t as i64, 0) { 629 | dt 630 | } else { 631 | DateTime::::MIN_UTC 632 | }, 633 | )) 634 | } 635 | if let Some(t) = claims.expires { 636 | c.expires = Some(metav1::Time( 637 | if let LocalResult::Single(dt) = Utc.timestamp_opt(t as i64, 0) { 638 | dt 639 | } else { 640 | DateTime::::MIN_UTC 641 | }, 642 | )) 643 | } 644 | 645 | let mut status = v1alpha1::ProviderStatus { 646 | public_key, 647 | descriptive_name: None, 648 | contract_id: "".to_string(), 649 | 650 | vendor: "".to_string(), 651 | reversion: None, 652 | version: None, 653 | claims: c, 654 | 655 | architecture_targets: Vec::new(), 656 | 657 | conditions: Vec::new(), 658 | instance_id: "".to_string(), 659 | }; 660 | if let Some(meta) = claims.metadata { 661 | status.descriptive_name = meta.name; 662 | status.reversion = meta.rev; 663 | status.version = meta.ver; 664 | status.vendor = meta.vendor; 665 | status.contract_id = meta.capid; 666 | status.architecture_targets = meta.target_hashes.into_keys().collect(); 667 | status.architecture_targets.sort_by(|x, y| x.cmp(&y)); 668 | } 669 | 670 | let data = serde_json::json!({ "status": status }); 671 | 672 | if let Some(s) = g.status.clone() { 673 | if s == status { 674 | return Ok(Action::await_change()); 675 | } 676 | } 677 | 678 | if let Err(err) = ctx 679 | .provider_client 680 | .patch_status( 681 | g.name_any().as_str(), 682 | &PatchParams::default(), 683 | &Patch::Merge(&data), 684 | ) 685 | .await 686 | { 687 | error!("patch provider failed: {:?}", err); 688 | } 689 | } 690 | Err(err) => { 691 | error!("reconcile provider failed: {:?}", err); 692 | } 693 | } 694 | 695 | Ok(Action::await_change()) 696 | } 697 | 698 | async fn delete_provider( 699 | provider: Arc, 700 | ctx: Arc, 701 | ) -> Result { 702 | let status = if let Some(status) = provider.status.clone() { 703 | if status.public_key == "" { 704 | return Ok(Action::await_change()); 705 | } 706 | status 707 | } else { 708 | return Ok(Action::await_change()); 709 | }; 710 | 711 | if let Err(err) = ctx 712 | .host 713 | .stop_provider(provider.spec.link.clone(), status.public_key.clone()) 714 | .await 715 | { 716 | error!("delete provider failed: {:?}", err); 717 | return Ok(Action::requeue(Duration::from_secs(10))); 718 | } 719 | 720 | Ok(Action::await_change()) 721 | } 722 | --------------------------------------------------------------------------------