feat(k8s_app): OperatorhubCatalogSourceScore can now install the operatorhub catalogsource on a cluster that already has operator lifecycle manager installed
This commit is contained in:
18
examples/operatorhub_catalog/Cargo.toml
Normal file
18
examples/operatorhub_catalog/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "example-operatorhub-catalogsource"
|
||||||
|
edition = "2024"
|
||||||
|
version.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
harmony = { path = "../../harmony" }
|
||||||
|
harmony_cli = { path = "../../harmony_cli" }
|
||||||
|
harmony_types = { path = "../../harmony_types" }
|
||||||
|
cidr = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
harmony_macros = { path = "../../harmony_macros" }
|
||||||
|
log = { workspace = true }
|
||||||
|
env_logger = { workspace = true }
|
||||||
|
url = { workspace = true }
|
||||||
23
examples/operatorhub_catalog/src/main.rs
Normal file
23
examples/operatorhub_catalog/src/main.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use harmony::{
|
||||||
|
inventory::Inventory,
|
||||||
|
modules::{k8s::apps::OperatorHubCatalogSourceScore, tenant::TenantScore},
|
||||||
|
topology::{tenant::TenantConfig, K8sAnywhereTopology},
|
||||||
|
};
|
||||||
|
use harmony_types::id::Id;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
|
||||||
|
let operatorhub_catalog = OperatorHubCatalogSourceScore::default();
|
||||||
|
|
||||||
|
harmony_cli::run(
|
||||||
|
Inventory::autoload(),
|
||||||
|
K8sAnywhereTopology::from_env(),
|
||||||
|
vec![Box::new(operatorhub_catalog)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use k8s_openapi::{
|
||||||
|
api::core::v1::{Affinity, Toleration},
|
||||||
|
apimachinery::pkg::apis::meta::v1::ObjectMeta,
|
||||||
|
};
|
||||||
|
use kube::CustomResource;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug)]
|
||||||
|
#[kube(
|
||||||
|
group = "operators.coreos.com",
|
||||||
|
version = "v1alpha1",
|
||||||
|
kind = "CatalogSource",
|
||||||
|
plural = "catalogsources",
|
||||||
|
namespaced = true,
|
||||||
|
schema = "disabled"
|
||||||
|
)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CatalogSourceSpec {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub address: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub config_map: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub description: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub display_name: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub grpc_pod_config: Option<GrpcPodConfig>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub icon: Option<Icon>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub image: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub priority: Option<i64>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub publisher: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub run_as_root: Option<bool>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub secrets: Option<Vec<String>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub source_type: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub update_strategy: Option<UpdateStrategy>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct GrpcPodConfig {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub affinity: Option<Affinity>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub extract_content: Option<ExtractContent>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub memory_target: Option<Value>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub node_selector: Option<BTreeMap<String, String>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub priority_class_name: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub security_context_config: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub tolerations: Option<Vec<Toleration>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ExtractContent {
|
||||||
|
pub cache_dir: String,
|
||||||
|
pub catalog_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Icon {
|
||||||
|
pub base64data: String,
|
||||||
|
pub mediatype: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct UpdateStrategy {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub registry_poll: Option<RegistryPoll>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RegistryPoll {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub interval: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CatalogSource {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
metadata: ObjectMeta::default(),
|
||||||
|
spec: CatalogSourceSpec {
|
||||||
|
address: None,
|
||||||
|
config_map: None,
|
||||||
|
description: None,
|
||||||
|
display_name: None,
|
||||||
|
grpc_pod_config: None,
|
||||||
|
icon: None,
|
||||||
|
image: None,
|
||||||
|
priority: None,
|
||||||
|
publisher: None,
|
||||||
|
run_as_root: None,
|
||||||
|
secrets: None,
|
||||||
|
source_type: None,
|
||||||
|
update_strategy: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CatalogSourceSpec {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
address: None,
|
||||||
|
config_map: None,
|
||||||
|
description: None,
|
||||||
|
display_name: None,
|
||||||
|
grpc_pod_config: None,
|
||||||
|
icon: None,
|
||||||
|
image: None,
|
||||||
|
priority: None,
|
||||||
|
publisher: None,
|
||||||
|
run_as_root: None,
|
||||||
|
secrets: None,
|
||||||
|
source_type: None,
|
||||||
|
update_strategy: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
harmony/src/modules/k8s/apps/crd/mod.rs
Normal file
4
harmony/src/modules/k8s/apps/crd/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
mod catalogsources_operators_coreos_com;
|
||||||
|
pub use catalogsources_operators_coreos_com::*;
|
||||||
|
|
||||||
4
harmony/src/modules/k8s/apps/mod.rs
Normal file
4
harmony/src/modules/k8s/apps/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
mod operatorhub;
|
||||||
|
pub use operatorhub::*;
|
||||||
|
pub mod crd;
|
||||||
|
|
||||||
107
harmony/src/modules/k8s/apps/operatorhub.rs
Normal file
107
harmony/src/modules/k8s/apps/operatorhub.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Write operatorhub catalog score
|
||||||
|
// for now this will only support on OKD with the default catalog and operatorhub setup and does not verify OLM state or anything else. Very opinionated and bare-bones to start
|
||||||
|
|
||||||
|
use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::interpret::Interpret;
|
||||||
|
use crate::modules::k8s::apps::crd::{
|
||||||
|
CatalogSource, CatalogSourceSpec, RegistryPoll, UpdateStrategy,
|
||||||
|
};
|
||||||
|
use crate::modules::k8s::resource::K8sResourceScore;
|
||||||
|
use crate::score::Score;
|
||||||
|
use crate::topology::{K8sclient, Topology};
|
||||||
|
|
||||||
|
/// Installs the CatalogSource in a cluster which already has the required services and CRDs installed.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use harmony::modules::k8s::apps::OperatorHubCatalogSourceScore;
|
||||||
|
///
|
||||||
|
/// let score = OperatorHubCatalogSourceScore::default();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Required services:
|
||||||
|
/// - catalog-operator
|
||||||
|
/// - olm-operator
|
||||||
|
///
|
||||||
|
/// They are installed by default with OKD/Openshift
|
||||||
|
///
|
||||||
|
/// **Warning** : this initial implementation does not manage the dependencies. They must already
|
||||||
|
/// exist in the cluster.
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct OperatorHubCatalogSourceScore {
|
||||||
|
pub name: String,
|
||||||
|
pub namespace: String,
|
||||||
|
pub image: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OperatorHubCatalogSourceScore {
|
||||||
|
pub fn new(name: &str, namespace: &str, image: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
namespace: namespace.to_string(),
|
||||||
|
image: image.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OperatorHubCatalogSourceScore {
|
||||||
|
/// This default implementation will create this k8s resource :
|
||||||
|
///
|
||||||
|
/// ```yaml
|
||||||
|
/// apiVersion: operators.coreos.com/v1alpha1
|
||||||
|
/// kind: CatalogSource
|
||||||
|
/// metadata:
|
||||||
|
/// name: operatorhubio-catalog
|
||||||
|
/// namespace: openshift-marketplace
|
||||||
|
/// spec:
|
||||||
|
/// sourceType: grpc
|
||||||
|
/// image: quay.io/operatorhubio/catalog:latest
|
||||||
|
/// displayName: Operatorhub Operators
|
||||||
|
/// publisher: OperatorHub.io
|
||||||
|
/// updateStrategy:
|
||||||
|
/// registryPoll:
|
||||||
|
/// interval: 60m
|
||||||
|
/// ```
|
||||||
|
fn default() -> Self {
|
||||||
|
OperatorHubCatalogSourceScore {
|
||||||
|
name: "operatorhubio-catalog".to_string(),
|
||||||
|
namespace: "openshift-marketplace".to_string(),
|
||||||
|
image: "quay.io/operatorhubio/catalog:latest".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology + K8sclient> Score<T> for OperatorHubCatalogSourceScore {
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
let metadata = ObjectMeta {
|
||||||
|
name: Some(self.name.clone()),
|
||||||
|
namespace: Some(self.namespace.clone()),
|
||||||
|
..ObjectMeta::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let spec = CatalogSourceSpec {
|
||||||
|
source_type: Some("grpc".to_string()),
|
||||||
|
image: Some(self.image.clone()),
|
||||||
|
display_name: Some("Operatorhub Operators".to_string()),
|
||||||
|
publisher: Some("OperatorHub.io".to_string()),
|
||||||
|
update_strategy: Some(UpdateStrategy {
|
||||||
|
registry_poll: Some(RegistryPoll {
|
||||||
|
interval: Some("60m".to_string()),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
..CatalogSourceSpec::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let catalog_source = CatalogSource {
|
||||||
|
metadata,
|
||||||
|
spec: spec,
|
||||||
|
};
|
||||||
|
|
||||||
|
K8sResourceScore::single(catalog_source, Some(self.namespace.clone())).create_interpret()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
format!("OperatorHubCatalogSourceScore({})", self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,3 +2,4 @@ pub mod deployment;
|
|||||||
pub mod ingress;
|
pub mod ingress;
|
||||||
pub mod namespace;
|
pub mod namespace;
|
||||||
pub mod resource;
|
pub mod resource;
|
||||||
|
pub mod apps;
|
||||||
|
|||||||
Reference in New Issue
Block a user