k8s-gcr-auth-helper 0.1.10

A Kubernetes authentication helper to expose per-user credentials as Image Pull Secrets for Google Container Registry
Documentation
use anyhow::Error;
use clap::{Arg, ArgMatches};
use k8s_openapi::api::apps::v1::Deployment;
use k8s_openapi::api::core::v1::{Secret, ServiceAccount};
use k8s_openapi::api::rbac::v1::{ClusterRole, ClusterRoleBinding};
use kube::api::{DeleteParams, PostParams};
use kube::client::APIClient;
use kube::Api;

use async_trait::async_trait;

#[derive(Debug, PartialEq, Clone)]
pub struct Targets {
    namespace: String,
    registry_ulrs: Vec<String>,
}

impl Targets {
    pub fn args() -> Vec<Arg<'static, 'static>> {
        vec![
            Arg::with_name("registry_url")
                .multiple(true)
                .short("r")
                .long("registry-url")
                .number_of_values(1)
                .default_value("https://gcr.io")
                .value_name("URL")
                .help("The registry to forward authentication for"),
            Arg::with_name("namespace")
                .short("n")
                .long("namespace")
                .takes_value(true)
                .value_name("NAMESPACE")
                .help("The namespace to create the secret in, if not current namespace"),
        ]
    }

    pub fn new(namespace: &str, registry_urls: &[&str]) -> Self {
        Self {
            namespace: namespace.into(),
            registry_ulrs: registry_urls.iter().map(|s| (*s).to_string()).collect(),
        }
    }

    pub fn from_matches(matches: &ArgMatches, default_namespace: &str) -> Self {
        Self {
            namespace: matches
                .value_of("namespace")
                .map(From::from)
                .unwrap_or_else(|| default_namespace.into()),
            registry_ulrs: matches
                .values_of("registry_url")
                .map(|v| v.map(|s| s.to_string()).collect())
                .unwrap_or_else(Vec::new),
        }
    }

    pub fn namespace(&self) -> &str {
        &self.namespace
    }

    pub fn registry_urls(&self) -> Vec<&str> {
        self.registry_ulrs.iter().map(AsRef::as_ref).collect()
    }
}

#[async_trait]
pub trait KubeCrud {
    async fn upsert(&self, client: &APIClient, namespace: &str, name: &str) -> anyhow::Result<()>;
    async fn delete(client: &APIClient, namespace: &str, name: &str) -> anyhow::Result<()>;
}

#[async_trait]
impl KubeCrud for Secret {
    async fn upsert(&self, client: &APIClient, namespace: &str, name: &str) -> anyhow::Result<()> {
        let pp = PostParams::default();
        let secrets: Api<Secret> = Api::namespaced(client.clone(), namespace);
        if secrets.get(&name).await.is_ok() {
            secrets.replace(&name, &pp, &self).await?;
            debug!("Secret {} updated", name);
        } else {
            secrets.create(&pp, &self).await?;
            debug!("Secret {} created", name);
        }
        Ok(())
    }

    async fn delete(client: &APIClient, namespace: &str, name: &str) -> anyhow::Result<()> {
        let dp = DeleteParams::default();
        let objects: Api<Secret> = Api::namespaced(client.clone(), namespace);
        if objects.delete(&name, &dp).await.is_ok() {
            debug!("Secret {} deleted", name);
        }
        Ok(())
    }
}

#[async_trait]
impl KubeCrud for ServiceAccount {
    async fn upsert(&self, client: &APIClient, namespace: &str, name: &str) -> anyhow::Result<()> {
        let pp = PostParams::default();
        let accounts: Api<ServiceAccount> = Api::namespaced(client.clone(), namespace);
        if let Ok(existing) = accounts.get(&name).await {
            let object = self.clone();
            let object = ServiceAccount {
                metadata: object.metadata.or(existing.metadata),
                automount_service_account_token: object
                    .automount_service_account_token
                    .or(existing.automount_service_account_token),
                secrets: object.secrets.or(existing.secrets),
                image_pull_secrets: object.image_pull_secrets.or(existing.image_pull_secrets),
            };
            accounts.replace(&name, &pp, &object).await?;
            debug!("ServiceAccount {} updated", name);
        } else {
            accounts.create(&pp, &self).await?;
            debug!("ServiceAccount {} created", name);
        }
        Ok(())
    }
    async fn delete(client: &APIClient, namespace: &str, name: &str) -> anyhow::Result<()> {
        let dp = DeleteParams::default();
        let objects: Api<ServiceAccount> = Api::namespaced(client.clone(), namespace);
        if objects.delete(&name, &dp).await.is_ok() {
            debug!("ServiceAccount {} deleted", name);
        }
        Ok(())
    }
}

#[async_trait]
impl KubeCrud for Deployment {
    async fn upsert(&self, client: &APIClient, namespace: &str, name: &str) -> Result<(), Error> {
        let pp = PostParams::default();
        let deployments: Api<Deployment> = Api::namespaced(client.clone(), namespace);
        if deployments.get(&name).await.is_ok() {
            if deployments.replace(&name, &pp, &self).await.is_err() {
                debug!(
                    "Deployment {} cannot be updated, trying to delete and recreate",
                    name
                );
                deployments.delete(&name, &DeleteParams::default()).await?;
                deployments.create(&pp, &self).await?;
            };
            debug!("Deployment {} updated", name);
        } else {
            deployments.create(&pp, &self).await?;
            debug!("Deployment {} created", name);
        }
        Ok(())
    }
    async fn delete(client: &APIClient, namespace: &str, name: &str) -> anyhow::Result<()> {
        let dp = DeleteParams::default();
        let objects: Api<Deployment> = Api::namespaced(client.clone(), namespace);
        if objects.delete(&name, &dp).await.is_ok() {
            debug!("Deployment {} deleted", name);
        }
        Ok(())
    }
}

#[async_trait]
impl KubeCrud for ClusterRoleBinding {
    async fn upsert(&self, client: &APIClient, _namespace: &str, name: &str) -> Result<(), Error> {
        let pp = PostParams::default();
        let bindings: Api<ClusterRoleBinding> = Api::all(client.clone());
        if bindings.get(&name).await.is_ok() {
            bindings.replace(&name, &pp, &self).await?;
            debug!("ClusterRoleBinding {} updated", name);
        } else {
            bindings.create(&pp, &self).await?;
            debug!("ClusterRoleBinding {} created", name);
        }
        Ok(())
    }
    async fn delete(client: &APIClient, _namespace: &str, name: &str) -> anyhow::Result<()> {
        let dp = DeleteParams::default();
        let objects: Api<ClusterRoleBinding> = Api::all(client.clone());
        if objects.delete(&name, &dp).await.is_ok() {
            debug!("ClusterRoleBinding {} deleted", name);
        }
        Ok(())
    }
}

#[async_trait]
impl KubeCrud for ClusterRole {
    async fn upsert(&self, client: &APIClient, _namespace: &str, name: &str) -> Result<(), Error> {
        let pp = PostParams::default();
        let bindings: Api<ClusterRole> = Api::all(client.clone());
        if bindings.get(&name).await.is_ok() {
            bindings.replace(&name, &pp, &self).await?;
            debug!("ClusterRole {} updated", name);
        } else {
            bindings.create(&pp, &self).await?;
            debug!("ClusterRole {} created", name);
        }
        Ok(())
    }
    async fn delete(client: &APIClient, _namespace: &str, name: &str) -> anyhow::Result<()> {
        let dp = DeleteParams::default();
        let objects: Api<ClusterRole> = Api::all(client.clone());
        if objects.delete(&name, &dp).await.is_ok() {
            debug!("ClusterRole {} deleted", name);
        }
        Ok(())
    }
}