From 14e24bb3920ff9139a8f769f2d0a045b367525a7 Mon Sep 17 00:00:00 2001 From: Riccardo Boero <ribo@nilu.no> Date: Mon, 25 Nov 2024 15:57:31 +0100 Subject: [PATCH] Add first draft of main functions --- Manifest.toml | 194 ++++++++++++++++++++++++++++++++++- Project.toml | 8 ++ src/JuliaKubernetesClient.jl | 32 +++++- src/deployment/Cluster.jl | 103 +++++++++++++++++++ src/deployment/Pod.jl | 142 +++++++++++++++++++++++++ src/deployment/Scale.jl | 147 ++++++++++++++++++++++++++ src/deployment/Service.jl | 103 +++++++++++++++++++ src/utils/ConfigMap.jl | 177 ++++++++++++++++++++++++++++++++ src/utils/CustomResource.jl | 92 +++++++++++++++++ src/utils/Monitoring.jl | 83 +++++++++++++++ src/utils/Namespaces.jl | 107 +++++++++++++++++++ 11 files changed, 1185 insertions(+), 3 deletions(-) create mode 100644 src/deployment/Cluster.jl create mode 100644 src/deployment/Pod.jl create mode 100644 src/deployment/Scale.jl create mode 100644 src/deployment/Service.jl create mode 100644 src/utils/ConfigMap.jl create mode 100644 src/utils/CustomResource.jl create mode 100644 src/utils/Monitoring.jl create mode 100644 src/utils/Namespaces.jl diff --git a/Manifest.toml b/Manifest.toml index c33624a..e3fe322 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -2,6 +2,196 @@ julia_version = "1.11.1" manifest_format = "2.0" -project_hash = "da39a3ee5e6b4b0d3255bfef95601890afd80709" +project_hash = "c52613f526f18730332842a14fe91075eef6020c" -[deps] +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" + +[[deps.BitFlags]] +git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d" +uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35" +version = "0.1.9" + +[[deps.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.6" + +[[deps.ConcurrentUtilities]] +deps = ["Serialization", "Sockets"] +git-tree-sha1 = "ea32b83ca4fefa1768dc84e504cc0a94fb1ab8d1" +uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" +version = "2.4.2" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" + +[[deps.ExceptionUnwrapping]] +deps = ["Test"] +git-tree-sha1 = "d36f682e590a83d63d1c7dbd287573764682d12a" +uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" +version = "0.1.11" + +[[deps.HTTP]] +deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] +git-tree-sha1 = "ae350b8225575cc3ea385d4131c81594f86dfe4f" +uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" +version = "1.10.12" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "be3dc50a92e5a386872a493a10050136d4703f9b" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.6.1" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + +[[deps.LoggingExtras]] +deps = ["Dates", "Logging"] +git-tree-sha1 = "f02b56007b064fbfddb4c9cd60161b6dd0f40df3" +uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" +version = "1.1.0" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + +[[deps.MbedTLS]] +deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"] +git-tree-sha1 = "c067a280ddc25f196b5e7df3877c6b226d390aaf" +uuid = "739be429-bea8-5141-9913-cc70e7f3736d" +version = "1.1.9" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.6+0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.12.12" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OpenSSL]] +deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"] +git-tree-sha1 = "38cb508d080d21dc1128f7fb04f20387ed4c0af4" +uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c" +version = "1.4.3" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10" +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.0.15+1" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.1" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.3" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" + +[[deps.SimpleBufferStream]] +git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1" +uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" +version = "1.2.0" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +version = "1.11.0" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" + +[[deps.TranscodingStreams]] +git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.11.3" + +[[deps.URIs]] +git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b" +uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" +version = "1.5.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" diff --git a/Project.toml b/Project.toml index d1d5c1f..72f3e73 100644 --- a/Project.toml +++ b/Project.toml @@ -2,3 +2,11 @@ name = "JuliaKubernetesClient" uuid = "7f16e22b-d142-4228-abe5-a6c51af6d3a2" authors = ["Riccardo Boero <ribo@nilu.no>"] version = "0.1.0" + +[deps] +HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" + +[compat] +HTTP = "1.10.12" +JSON = "0.21.4" diff --git a/src/JuliaKubernetesClient.jl b/src/JuliaKubernetesClient.jl index 861a3dd..14bfbdb 100644 --- a/src/JuliaKubernetesClient.jl +++ b/src/JuliaKubernetesClient.jl @@ -1,5 +1,35 @@ module JuliaKubernetesClient -greet() = print("Hello World!") + using HTTP, JSON + + struct KubernetesConfig + host::String + token::String + namespace::String + end + + include("deployment/Cluster.jl") + export test_connection, get_cluster_info, authenticate_cluster + + include("deployment/Pod.jl") + export create_pod, list_pods, delete_pod, exec_pod_command + + include("deployment/Scale.jl") + export create_deployment, list_deployments, scale_deployment, delete_deployment + + include("deployment/Service.jl") + export create_service, list_services, delete_service + + include("utils/ConfigMap.jl") + export create_configmap, list_configmaps, delete_configmap, create_secret, list_secrets, delete_secret + + include("utils/Namespaces.jl") + export create_namespace, list_namespaces, delete_namespace + + include("utils/Monitoring.jl") + export get_pod_logs, watch_pod_events + + include("utils/CustomResource.jl") + export apply_manifest, delete_resource end # module JuliaKubernetesClient diff --git a/src/deployment/Cluster.jl b/src/deployment/Cluster.jl new file mode 100644 index 0000000..1072707 --- /dev/null +++ b/src/deployment/Cluster.jl @@ -0,0 +1,103 @@ +""" +test_connection(host::String; token::String = "") -> Bool +Tests connectivity to the Kubernetes API server. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `token`: The authentication token for the API (default: ""). + +Returns: +`true` if the connection is successful, `false` otherwise. +""" +function test_connection(host::String; token::String = "") + # Construct the URL for the Kubernetes health endpoint + url = "$host/healthz" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + try + # Make the HTTP GET request + response = HTTP.get(url, headers) + return response.status == 200 + catch e + # If any error occurs, return false + return false + end +end + +function test_connection(config::KubernetesConfig) + test_connection(config.host; token=config.token) +end + + +""" +get_cluster_info(host::String; token::String = "") -> Dict +Retrieves basic information about the connected Kubernetes cluster. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary with information about the Kubernetes cluster. +""" +function get_cluster_info(host::String; token::String = "") + # Fetch version info + version_url = "$host/version" + headers = ["Authorization" => "Bearer $token"] + + try + # Query the Kubernetes version + version_response = HTTP.get(version_url, headers) + version_info = response.status == 200 ? JSON.read(version_response.body) : Dict() + + # Query the nodes + nodes_url = "$host/api/v1/nodes" + nodes_response = HTTP.get(nodes_url, headers) + nodes_info = nodes_response.status == 200 ? JSON.read(nodes_response.body)["items"] : [] + + return Dict("version" => version_info, "nodes" => nodes_info) + catch e + error("Failed to retrieve cluster info: $e") + end +end + +function get_cluster_info(config::KubernetesConfig) + get_cluster_info(config.host; token=config.token) +end + + +""" +authenticate_cluster(kubeconfig_path::String, context::String = "") -> KubernetesConfig +Authenticates with the Kubernetes cluster using a kubeconfig file or token. + +Arguments: +- `kubeconfig_path`: Path to the kubeconfig file. +- `context`: The context to use from the kubeconfig file (default: ""). + +Returns: +A `KubernetesConfig` object with the host, token, and namespace for the cluster. +""" +function authenticate_cluster(kubeconfig_path::String, context::String = "") + # Load the kubeconfig file + kubeconfig = JSON.read(open(kubeconfig_path, "r") |> read, Dict) + + # Select the current context + current_context = context == "" ? kubeconfig["current-context"] : context + context_info = filter(c -> c["name"] == current_context, kubeconfig["contexts"])[1]["context"] + + # Extract cluster and user info + cluster_info = filter(c -> c["name"] == context_info["cluster"], kubeconfig["clusters"])[1]["cluster"] + user_info = filter(u -> u["name"] == context_info["user"], kubeconfig["users"])[1]["user"] + + host = cluster_info["server"] + token = user_info["token"] + namespace = get(context_info, "namespace", "default") + + return KubernetesConfig(host, token, namespace) +end + +function authenticate_cluster(token::String, host::String, namespace::String = "default") + return KubernetesConfig(host, token, namespace) +end diff --git a/src/deployment/Pod.jl b/src/deployment/Pod.jl new file mode 100644 index 0000000..d968452 --- /dev/null +++ b/src/deployment/Pod.jl @@ -0,0 +1,142 @@ +""" +list_pods(host::String, namespace::String = "default"; token::String = "") -> Vector{Dict} +Lists all pods in the specified namespace for the given Kubernetes cluster host. + +Arguments: +- `host`: The base URL of the Kubernetes API server (e.g., "https://my-cluster.example.com"). +- `namespace`: The namespace to query pods from (default: "default"). +- `token`: The authentication token for the API (default: ""). + +Returns: +A vector of dictionaries, each representing a pod. +""" +function list_pods(host::String, namespace::String = "default"; token::String = "") + # Construct the URL for the Kubernetes API endpoint + url = "$host/api/v1/namespaces/$namespace/pods" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + # Make the HTTP request + response = HTTP.get(url, headers) + + # Handle response + if response.status == 200 + return JSON.read(response.body)["items"] + else + error("Failed to list pods: $(response.status) $(response.body)") + end +end + +function list_pods(config::KubernetesConfig) + list_pods(config.host, config.namespace; token=config.token) +end + + +""" +create_pod(host::String, namespace::String, pod_manifest::Dict; token::String = "") -> Dict +Creates a new pod in the specified namespace using the provided pod manifest. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace where the pod will be created. +- `pod_manifest`: A dictionary representing the pod's YAML manifest. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary with the details of the created pod. +""" +function create_pod(host::String, namespace::String, pod_manifest::Dict; token::String = "") + # Construct the URL for the Kubernetes API endpoint + url = "$host/api/v1/namespaces/$namespace/pods" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token", "Content-Type" => "application/json"] + + # Make the HTTP POST request + response = HTTP.post(url, headers, body=JSON.json(pod_manifest)) + + # Handle response + if response.status == 201 + return JSON.read(response.body) + else + error("Failed to create pod: $(response.status) $(response.body)") + end +end + +function create_pod(config::KubernetesConfig, pod_manifest::Dict) + create_pod(config.host, config.namespace, pod_manifest; token=config.token) +end + +""" +delete_pod(host::String, namespace::String, pod_name::String; token::String = "") -> Dict +Deletes the specified pod in the given namespace. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace of the pod to be deleted. +- `pod_name`: The name of the pod to be deleted. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary containing the details of the deletion operation. +""" +function delete_pod(host::String, namespace::String, pod_name::String; token::String = "") + # Construct the URL for the Kubernetes API endpoint + url = "$host/api/v1/namespaces/$namespace/pods/$pod_name" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + # Make the HTTP DELETE request + response = HTTP.delete(url, headers) + + # Handle response + if response.status == 200 + return JSON.read(response.body) + else + error("Failed to delete pod: $(response.status) $(response.body)") + end +end + +function delete_pod(config::KubernetesConfig, pod_name::String) + delete_pod(config.host, config.namespace, pod_name; token=config.token) +end + +""" +exec_pod_command(host::String, namespace::String, pod_name::String, container::String, command::Vector{String}; token::String = "") -> String +Executes a command inside a container within a specified pod. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace of the pod. +- `pod_name`: The name of the pod. +- `container`: The name of the container in the pod to execute the command. +- `command`: A vector of strings representing the command to execute. +- `token`: The authentication token for the API (default: ""). + +Returns: +The output of the executed command as a string. +""" +function exec_pod_command(host::String, namespace::String, pod_name::String, container::String, command::Vector{String}; token::String = "") + # Construct the URL for the Kubernetes API exec endpoint + command_query = join(["command=$c" for c in command], "&") + url = "$host/api/v1/namespaces/$namespace/pods/$pod_name/exec?container=$container&$command_query&stdout=true&stderr=true" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + # Make the HTTP GET request + response = HTTP.get(url, headers) + + # Handle response + if response.status == 200 + return String(response.body) + else + error("Failed to execute command in pod: $(response.status) $(response.body)") + end +end + +function exec_pod_command(config::KubernetesConfig, pod_name::String, container::String, command::Vector{String}) + exec_pod_command(config.host, config.namespace, pod_name, container, command; token=config.token) +end diff --git a/src/deployment/Scale.jl b/src/deployment/Scale.jl new file mode 100644 index 0000000..6f85c5e --- /dev/null +++ b/src/deployment/Scale.jl @@ -0,0 +1,147 @@ +""" +create_deployment(host::String, namespace::String, deployment_manifest::Dict; token::String = "") -> Dict +Creates a new deployment in the specified namespace using the provided deployment manifest. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace where the deployment will be created. +- `deployment_manifest`: A dictionary representing the deployment's YAML manifest. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary with the details of the created deployment. +""" +function create_deployment(host::String, namespace::String, deployment_manifest::Dict; token::String = "") + # Construct the URL for the Kubernetes deployments endpoint + url = "$host/apis/apps/v1/namespaces/$namespace/deployments" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token", "Content-Type" => "application/json"] + + # Make the HTTP POST request + response = HTTP.post(url, headers, body=JSON.json(deployment_manifest)) + + # Handle response + if response.status == 201 + return JSON.read(response.body) + else + error("Failed to create deployment: $(response.status) $(response.body)") + end +end + +function create_deployment(config::KubernetesConfig, deployment_manifest::Dict) + create_deployment(config.host, config.namespace, deployment_manifest; token=config.token) +end + +""" +list_deployments(host::String, namespace::String = ""; token::String = "") -> Vector{Dict} +Lists deployments in the specified namespace or across the cluster. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace to query deployments from (default: all namespaces). +- `token`: The authentication token for the API (default: ""). + +Returns: +A vector of dictionaries, each representing a deployment. +""" +function list_deployments(host::String, namespace::String = ""; token::String = "") + # Construct the URL for the Kubernetes deployments endpoint + url = namespace == "" ? "$host/apis/apps/v1/deployments" : "$host/apis/apps/v1/namespaces/$namespace/deployments" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + # Make the HTTP GET request + response = HTTP.get(url, headers) + + # Handle response + if response.status == 200 + return JSON.read(response.body)["items"] + else + error("Failed to list deployments: $(response.status) $(response.body)") + end +end + +function list_deployments(config::KubernetesConfig) + list_deployments(config.host, config.namespace; token=config.token) +end + +""" +scale_deployment(host::String, namespace::String, deployment_name::String, replicas::Int; token::String = "") -> Dict +Scales a deployment to the specified number of replicas. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace of the deployment. +- `deployment_name`: The name of the deployment to scale. +- `replicas`: The desired number of replicas. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary containing the updated deployment details. +""" +function scale_deployment(host::String, namespace::String, deployment_name::String, replicas::Int; token::String = "") + # Construct the URL for the specific deployment's scale subresource + url = "$host/apis/apps/v1/namespaces/$namespace/deployments/$deployment_name/scale" + + # Create the scale payload + scale_payload = Dict( + "apiVersion" => "apps/v1", + "kind" => "Scale", + "metadata" => Dict("name" => deployment_name, "namespace" => namespace), + "spec" => Dict("replicas" => replicas) + ) + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token", "Content-Type" => "application/json"] + + # Make the HTTP PUT request + response = HTTP.put(url, headers, body=JSON.json(scale_payload)) + + # Handle response + if response.status == 200 + return JSON.read(response.body) + else + error("Failed to scale deployment: $(response.status) $(response.body)") + end +end + +function scale_deployment(config::KubernetesConfig, deployment_name::String, replicas::Int) + scale_deployment(config.host, config.namespace, deployment_name, replicas; token=config.token) +end + +""" +delete_deployment(host::String, namespace::String, deployment_name::String; token::String = "") -> Dict +Deletes the specified deployment and its associated pods. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace of the deployment. +- `deployment_name`: The name of the deployment to delete. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary containing the details of the deletion operation. +""" +function delete_deployment(host::String, namespace::String, deployment_name::String; token::String = "") + # Construct the URL for the specific deployment + url = "$host/apis/apps/v1/namespaces/$namespace/deployments/$deployment_name" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + # Make the HTTP DELETE request + response = HTTP.delete(url, headers) + + # Handle response + if response.status == 200 + return JSON.read(response.body) + else + error("Failed to delete deployment: $(response.status) $(response.body)") + end +end + +function delete_deployment(config::KubernetesConfig, deployment_name::String) + delete_deployment(config.host, config.namespace, deployment_name; token=config.token) +end diff --git a/src/deployment/Service.jl b/src/deployment/Service.jl new file mode 100644 index 0000000..f88b523 --- /dev/null +++ b/src/deployment/Service.jl @@ -0,0 +1,103 @@ +""" +create_service(host::String, namespace::String, service_manifest::Dict; token::String = "") -> Dict +Creates a new Service in the specified namespace using the provided service manifest. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace where the service will be created. +- `service_manifest`: A dictionary representing the Service's YAML manifest. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary with the details of the created Service. +""" +function create_service(host::String, namespace::String, service_manifest::Dict; token::String = "") + # Construct the URL for the Kubernetes Services endpoint + url = "$host/api/v1/namespaces/$namespace/services" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token", "Content-Type" => "application/json"] + + # Make the HTTP POST request + response = HTTP.post(url, headers, body=JSON.json(service_manifest)) + + # Handle response + if response.status == 201 + return JSON.read(response.body) + else + error("Failed to create service: $(response.status) $(response.body)") + end +end + +function create_service(config::KubernetesConfig, service_manifest::Dict) + create_service(config.host, config.namespace, service_manifest; token=config.token) +end + +""" +list_services(host::String, namespace::String = ""; token::String = "") -> Vector{Dict} +Lists all Services in the specified namespace or across the cluster. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace to query Services from (default: all namespaces). +- `token`: The authentication token for the API (default: ""). + +Returns: +A vector of dictionaries, each representing a Service. +""" +function list_services(host::String, namespace::String = ""; token::String = "") + # Construct the URL for the Kubernetes Services endpoint + url = namespace == "" ? "$host/api/v1/services" : "$host/api/v1/namespaces/$namespace/services" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + # Make the HTTP GET request + response = HTTP.get(url, headers) + + # Handle response + if response.status == 200 + return JSON.read(response.body)["items"] + else + error("Failed to list services: $(response.status) $(response.body)") + end +end + +function list_services(config::KubernetesConfig) + list_services(config.host, config.namespace; token=config.token) +end + +""" +delete_service(host::String, namespace::String, service_name::String; token::String = "") -> Dict +Deletes the specified Service in the given namespace. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace of the Service. +- `service_name`: The name of the Service to delete. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary containing the details of the deletion operation. +""" +function delete_service(host::String, namespace::String, service_name::String; token::String = "") + # Construct the URL for the specific Service + url = "$host/api/v1/namespaces/$namespace/services/$service_name" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + # Make the HTTP DELETE request + response = HTTP.delete(url, headers) + + # Handle response + if response.status == 200 + return JSON.read(response.body) + else + error("Failed to delete service: $(response.status) $(response.body)") + end +end + +function delete_service(config::KubernetesConfig, service_name::String) + delete_service(config.host, config.namespace, service_name; token=config.token) +end diff --git a/src/utils/ConfigMap.jl b/src/utils/ConfigMap.jl new file mode 100644 index 0000000..d375e7f --- /dev/null +++ b/src/utils/ConfigMap.jl @@ -0,0 +1,177 @@ +""" +create_configmap(host::String, namespace::String, configmap_manifest::Dict; token::String = "") -> Dict +Creates a ConfigMap in the specified namespace using the provided manifest. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace where the ConfigMap will be created. +- `configmap_manifest`: A dictionary representing the ConfigMap's YAML manifest. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary with the details of the created ConfigMap. +""" +function create_configmap(host::String, namespace::String, configmap_manifest::Dict; token::String = "") + url = "$host/api/v1/namespaces/$namespace/configmaps" + headers = ["Authorization" => "Bearer $token", "Content-Type" => "application/json"] + + response = HTTP.post(url, headers, body=JSON.json(configmap_manifest)) + + if response.status == 201 + return JSON.read(response.body) + else + error("Failed to create ConfigMap: $(response.status) $(response.body)") + end +end + +function create_configmap(config::KubernetesConfig, configmap_manifest::Dict) + create_configmap(config.host, config.namespace, configmap_manifest; token=config.token) +end + +""" +list_configmaps(host::String, namespace::String; token::String = "") -> Vector{Dict} +Lists all ConfigMaps in the specified namespace. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace to query ConfigMaps from. +- `token`: The authentication token for the API (default: ""). + +Returns: +A vector of dictionaries, each representing a ConfigMap. +""" +function list_configmaps(host::String, namespace::String; token::String = "") + url = "$host/api/v1/namespaces/$namespace/configmaps" + headers = ["Authorization" => "Bearer $token"] + + response = HTTP.get(url, headers) + + if response.status == 200 + return JSON.read(response.body)["items"] + else + error("Failed to list ConfigMaps: $(response.status) $(response.body)") + end +end + +function list_configmaps(config::KubernetesConfig) + list_configmaps(config.host, config.namespace; token=config.token) +end + +""" +delete_configmap(host::String, namespace::String, configmap_name::String; token::String = "") -> Dict +Deletes a specified ConfigMap in the given namespace. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace of the ConfigMap. +- `configmap_name`: The name of the ConfigMap to delete. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary containing the details of the deletion operation. +""" +function delete_configmap(host::String, namespace::String, configmap_name::String; token::String = "") + url = "$host/api/v1/namespaces/$namespace/configmaps/$configmap_name" + headers = ["Authorization" => "Bearer $token"] + + response = HTTP.delete(url, headers) + + if response.status == 200 + return JSON.read(response.body) + else + error("Failed to delete ConfigMap: $(response.status) $(response.body)") + end +end + +function delete_configmap(config::KubernetesConfig, configmap_name::String) + delete_configmap(config.host, config.namespace, configmap_name; token=config.token) +end + +""" +create_secret(host::String, namespace::String, secret_manifest::Dict; token::String = "") -> Dict +Creates a Secret in the specified namespace using the provided manifest. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace where the Secret will be created. +- `secret_manifest`: A dictionary representing the Secret's YAML manifest. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary with the details of the created Secret. +""" +function create_secret(host::String, namespace::String, secret_manifest::Dict; token::String = "") + url = "$host/api/v1/namespaces/$namespace/secrets" + headers = ["Authorization" => "Bearer $token", "Content-Type" => "application/json"] + + response = HTTP.post(url, headers, body=JSON.json(secret_manifest)) + + if response.status == 201 + return JSON.read(response.body) + else + error("Failed to create Secret: $(response.status) $(response.body)") + end +end + +function create_secret(config::KubernetesConfig, secret_manifest::Dict) + create_secret(config.host, config.namespace, secret_manifest; token=config.token) +end + +""" +list_secrets(host::String, namespace::String; token::String = "") -> Vector{Dict} +Lists all Secrets in the specified namespace. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace to query Secrets from. +- `token`: The authentication token for the API (default: ""). + +Returns: +A vector of dictionaries, each representing a Secret. +""" +function list_secrets(host::String, namespace::String; token::String = "") + url = "$host/api/v1/namespaces/$namespace/secrets" + headers = ["Authorization" => "Bearer $token"] + + response = HTTP.get(url, headers) + + if response.status == 200 + return JSON.read(response.body)["items"] + else + error("Failed to list Secrets: $(response.status) $(response.body)") + end +end + +function list_secrets(config::KubernetesConfig) + list_secrets(config.host, config.namespace; token=config.token) +end + +""" +delete_secret(host::String, namespace::String, secret_name::String; token::String = "") -> Dict +Deletes a specified Secret in the given namespace. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace of the Secret. +- `secret_name`: The name of the Secret to delete. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary containing the details of the deletion operation. +""" +function delete_secret(host::String, namespace::String, secret_name::String; token::String = "") + url = "$host/api/v1/namespaces/$namespace/secrets/$secret_name" + headers = ["Authorization" => "Bearer $token"] + + response = HTTP.delete(url, headers) + + if response.status == 200 + return JSON.read(response.body) + else + error("Failed to delete Secret: $(response.status) $(response.body)") + end +end + +function delete_secret(config::KubernetesConfig, secret_name::String) + delete_secret(config.host, config.namespace, secret_name; token=config.token) +end diff --git a/src/utils/CustomResource.jl b/src/utils/CustomResource.jl new file mode 100644 index 0000000..f541207 --- /dev/null +++ b/src/utils/CustomResource.jl @@ -0,0 +1,92 @@ +""" +apply_manifest(host::String, manifest::Dict; token::String = "") -> Dict +Applies a YAML/JSON manifest to create or update Kubernetes resources. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `manifest`: A dictionary representing the resource manifest (YAML or JSON format). +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary containing the details of the created or updated resource. +""" +function apply_manifest(host::String, manifest::Dict; token::String = "") + # Determine the URL based on the kind and metadata in the manifest + kind = manifest["kind"] + api_version = manifest["apiVersion"] + namespace = get(manifest["metadata"], "namespace", "") + name = manifest["metadata"]["name"] + + # Construct the API endpoint + resource_path = if kind in ["Pod", "Service", "ConfigMap", "Secret"] + "/api/v1" + else + "/apis/$api_version" + end + namespace_path = namespace == "" ? "" : "/namespaces/$namespace" + url = "$host$resource_path$namespace_path/$(lowercase(kind))s/$name" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token", "Content-Type" => "application/json"] + + # Determine if the resource already exists + exists_response = HTTP.request("GET", url, headers=headers; status_exception=false) + + # Use PUT to update if it exists, otherwise POST to create + if exists_response.status == 200 + response = HTTP.put(url, headers, body=JSON.json(manifest)) + else + # Adjust the URL for a POST (omit resource name) + url = replace(url, "/$name" => "") + response = HTTP.post(url, headers, body=JSON.json(manifest)) + end + + # Handle response + if response.status in [200, 201] + return JSON.read(response.body) + else + error("Failed to apply manifest: $(response.status) $(response.body)") + end +end + +function apply_manifest(config::KubernetesConfig, manifest::Dict) + apply_manifest(config.host, manifest; token=config.token) +end + +""" +delete_resource(host::String, resource_type::String, name::String; namespace::String = "", token::String = "") -> Dict +Deletes a Kubernetes resource by type and name. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `resource_type`: The type of the resource (e.g., "pod", "service", "configmap"). +- `name`: The name of the resource to delete. +- `namespace`: (Optional) The namespace of the resource. If omitted, it will target cluster-wide resources. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary containing the details of the deletion operation. +""" +function delete_resource(host::String, resource_type::String, name::String; namespace::String = "", token::String = "") + # Construct the API endpoint + namespace_path = namespace == "" ? "" : "/namespaces/$namespace" + url = "$host/api/v1$namespace_path/$(lowercase(resource_type))s/$name" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + # Make the HTTP DELETE request + response = HTTP.delete(url, headers) + + # Handle response + if response.status == 200 + return JSON.read(response.body) + else + error("Failed to delete resource: $(response.status) $(response.body)") + end +end + +function delete_resource(config::KubernetesConfig, resource_type::String, name::String) + delete_resource(config.host, resource_type, name; namespace=config.namespace, token=config.token) +end + diff --git a/src/utils/Monitoring.jl b/src/utils/Monitoring.jl new file mode 100644 index 0000000..d58ba11 --- /dev/null +++ b/src/utils/Monitoring.jl @@ -0,0 +1,83 @@ +""" +get_pod_logs(host::String, namespace::String, pod_name::String; container::String = "", token::String = "") -> String +Retrieves logs for a specific pod, optionally for a specific container. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace of the pod. +- `pod_name`: The name of the pod to retrieve logs for. +- `container`: (Optional) The name of the container within the pod to retrieve logs from. +- `token`: The authentication token for the API (default: ""). + +Returns: +A string containing the logs of the specified pod or container. +""" +function get_pod_logs(host::String, namespace::String, pod_name::String; container::String = "", token::String = "") + # Construct the URL for the logs endpoint + url = "$host/api/v1/namespaces/$namespace/pods/$pod_name/log" + if container != "" + url *= "?container=$container" + end + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + # Make the HTTP GET request + response = HTTP.get(url, headers) + + # Handle response + if response.status == 200 + return String(response.body) + else + error("Failed to get pod logs: $(response.status) $(response.body)") + end +end + +function get_pod_logs(config::KubernetesConfig, pod_name::String; container::String = "") + get_pod_logs(config.host, config.namespace, pod_name; container=container, token=config.token) +end + +""" +watch_pod_events(host::String, namespace::String; pod_name::String = "", token::String = "", callback::Function) -> Nothing +Watches events for a specific pod or namespace. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace`: The namespace to watch events from. +- `pod_name`: (Optional) The name of the pod to watch events for. If empty, watches all events in the namespace. +- `token`: The authentication token for the API (default: ""). +- `callback`: A function to call for each received event. The event data will be passed as an argument. + +Returns: +Nothing. +""" +function watch_pod_events(host::String, namespace::String; pod_name::String = "", token::String = "", callback::Function) + # Construct the URL for the events endpoint with the watch parameter + url = pod_name == "" ? + "$host/api/v1/namespaces/$namespace/events?watch=true" : + "$host/api/v1/namespaces/$namespace/pods/$pod_name/events?watch=true" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + # Establish a streaming HTTP connection + response = HTTP.request("GET", url, headers, response_stream=true) + + try + # Read the stream line by line + for line in eachline(response.body) + # Parse the event JSON and pass it to the callback + event = JSON.read(line) + callback(event) + end + catch e + error("Failed to watch pod events: $e") + finally + # Ensure the stream is closed + close(response.body) + end +end + +function watch_pod_events(config::KubernetesConfig; pod_name::String = "", callback::Function) + watch_pod_events(config.host, config.namespace; pod_name=pod_name, token=config.token, callback=callback) +end diff --git a/src/utils/Namespaces.jl b/src/utils/Namespaces.jl new file mode 100644 index 0000000..88449e8 --- /dev/null +++ b/src/utils/Namespaces.jl @@ -0,0 +1,107 @@ +""" +create_namespace(host::String, namespace_name::String; token::String = "") -> Dict +Creates a new namespace in the Kubernetes cluster. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace_name`: The name of the namespace to create. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary with the details of the created namespace. +""" +function create_namespace(host::String, namespace_name::String; token::String = "") + # Construct the URL for the Kubernetes Namespaces endpoint + url = "$host/api/v1/namespaces" + + # Define the namespace manifest + namespace_manifest = Dict( + "apiVersion" => "v1", + "kind" => "Namespace", + "metadata" => Dict("name" => namespace_name) + ) + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token", "Content-Type" => "application/json"] + + # Make the HTTP POST request + response = HTTP.post(url, headers, body=JSON.json(namespace_manifest)) + + # Handle response + if response.status == 201 + return JSON.read(response.body) + else + error("Failed to create namespace: $(response.status) $(response.body)") + end +end + +function create_namespace(config::KubernetesConfig, namespace_name::String) + create_namespace(config.host, namespace_name; token=config.token) +end + +""" +list_namespaces(host::String; token::String = "") -> Vector{Dict} +Lists all namespaces in the Kubernetes cluster. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `token`: The authentication token for the API (default: ""). + +Returns: +A vector of dictionaries, each representing a namespace. +""" +function list_namespaces(host::String; token::String = "") + # Construct the URL for the Kubernetes Namespaces endpoint + url = "$host/api/v1/namespaces" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + # Make the HTTP GET request + response = HTTP.get(url, headers) + + # Handle response + if response.status == 200 + return JSON.read(response.body)["items"] + else + error("Failed to list namespaces: $(response.status) $(response.body)") + end +end + +function list_namespaces(config::KubernetesConfig) + list_namespaces(config.host; token=config.token) +end + +""" +delete_namespace(host::String, namespace_name::String; token::String = "") -> Dict +Deletes a specified namespace from the Kubernetes cluster. + +Arguments: +- `host`: The base URL of the Kubernetes API server. +- `namespace_name`: The name of the namespace to delete. +- `token`: The authentication token for the API (default: ""). + +Returns: +A dictionary containing the details of the deletion operation. +""" +function delete_namespace(host::String, namespace_name::String; token::String = "") + # Construct the URL for the specific namespace + url = "$host/api/v1/namespaces/$namespace_name" + + # Set up authentication headers + headers = ["Authorization" => "Bearer $token"] + + # Make the HTTP DELETE request + response = HTTP.delete(url, headers) + + # Handle response + if response.status == 200 + return JSON.read(response.body) + else + error("Failed to delete namespace: $(response.status) $(response.body)") + end +end + +function delete_namespace(config::KubernetesConfig, namespace_name::String) + delete_namespace(config.host, namespace_name; token=config.token) +end -- GitLab