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