rexcli 0.18.4

Replix admin CLI tool
//
// Copyright (c) 2021 RepliXio Ltd. All rights reserved.
// Use is subject to license terms.
//

use std::collections::HashMap;
use std::ops::Not;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use serde_with::skip_serializing_none;
use uuid::Uuid;

pub const VERSION: &str = "2020-12-31";

pub static AWS_SUPPORTED_REGIONS: &[&str] = &[
    "ap-northeast-1",
    "ap-northeast-2",
    "ap-south-1",
    "ap-southeast-1",
    "ap-southeast-2",
    "ca-central-1",
    "eu-central-1",
    "eu-north-1",
    "eu-west-1",
    "eu-west-2",
    "eu-west-3",
    "sa-east-1",
    "us-east-1",
    "us-east-2",
    "us-west-1",
    "us-west-2",
];

pub static AZURE_SUPPORTED_REGIONS: &[&str] = &[
    "centralus",
    "eastus",
    "eastus2",
    "francecentral",
    "japaneast",
    "northeurope",
    "southeastasia",
    "uksouth",
    "westeurope",
    "westus2",
];

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Realm {
    pub name: String,
    pub created: DateTime<Utc>,
    #[serde(flatten)]
    pub realm: RealmType,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Realms {
    pub realms: Vec<Realm>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RealmCreate {
    pub name: String,
    #[serde(flatten)]
    pub realm: RealmTypeCreate,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
// #[serde(untagged)]
pub enum RealmType {
    Aws {
        role_arn: String,
    },
    Azure {
        subscription_id: String,
        tenant_id: String,
        client_id: String,
    },
}

// Sample Realms JSON
// {
//   "realms": [
//     {
//       "name": "azure",
//       "created": "2021-01-06T10:01:27.337Z",
//       "azure": {
//         "client_id": "ea52a75e-66cf-489f-b583-179319330019",
//         "tenant_id": "810a0f8d-2707-48e1-adc0-445e112b9af5",
//         "subscription_id": "a4196105-b2d7-4e6e-8855-34b4e68628be"
//       }
//     },
//     {
//       "name": "aws",
//       "created": "2021-01-06T10:01:27.262Z",
//       "aws": {
//         "role_arn": "arn:aws:iam::534414359901:role/ReplixCrossAccountRole-BigBrother-ReplixRole-SA3YYB64AE9U"
//       }
//     }
//   ]
// }

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
#[serde(untagged)]
pub enum RealmTypeCreate {
    Aws {
        r#type: String,
        role_arn: String,
        external_id: String,
    },
    Azure {
        r#type: String,
        subscription_id: String,
        tenant_id: String,
        client_id: String,
        client_secret: String,
    },
}

/// Sample JSON:
///
/// {
///     "name": "volume-aws-x2",
///     "replicas": [
///         {
///             "aws": {
///                "realm": "aws",
///                "region": "us-west-2",
///                "subnet": "subnet-00a719b41a68a3beb"
///            },
///            "description": "westside",
///           "name": "west",
///            "primary": true
///        },
///        {
///            "aws": {
///                "realm": "aws",
///                "region": "us-east-2",
///                "subnet": "subnet-0ce209ae7321ec243"
///            },
///            "description": "eastside",
///            "name": "east",
///            "primary": false
///        }
///    ],
///    "source": {
///        "aws_snapshot": {
///            "id": "snap-0d99a968836a36ed3",
///            "realm": "aws",
///            "region": "us-west-2"
///        }
///    }
/// }
#[skip_serializing_none]
#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct VolumeCreate {
    pub id: Option<Uuid>,
    pub name: String,
    pub size_gb: Option<u64>,
    pub description: Option<String>,
    pub replicas: Vec<ReplicaCreate>,
    pub source: Option<VolumeSource>,
}

#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct VolumeCreateExt {
    pub id: Option<Uuid>,
    pub name: String,
    pub size_gb: Option<u64>,
    pub provisioned_iops: Option<u64>,
    pub description: Option<String>,
    pub replicas: Vec<ReplicaCreate>,
    pub source: Option<VolumeSourceExt>,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct PrivateLinkNlb {
    pub private_link_endpoint_id: String,
    pub vendor_nlb: VendorPrivateLinkNlb,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum VendorPrivateLinkNlb {
    Aws(AwsPrivateLink),
    Azure(AzurePrivateLink),
}

impl Default for VendorPrivateLinkNlb {
    fn default() -> Self {
        Self::Aws(AwsPrivateLink::default())
    }
}

#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct AwsPrivateLink {
    pub service_id: String,
    pub subnet: String,
    pub vpc: String,
    pub target_group: String,
}

#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct AzurePrivateLink {
    pub service_id: String,
    pub backend_subnet_id: String,
    pub loadbalancer_id: String,
}

impl From<VolumeCreate> for Option<VolumeCreateExt> {
    fn from(volume_create: VolumeCreate) -> Self {
        Some(VolumeCreateExt {
            id: volume_create.id,
            description: volume_create.description,
            name: volume_create.name,
            size_gb: volume_create.size_gb,
            provisioned_iops: None,
            replicas: volume_create.replicas,
            source: volume_create.source.map(VolumeSourceExt::from),
        })
    }
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum VolumeSource {
    AwsSnapshot(AwsSnapshot),
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum VolumeSourceExt {
    AwsSnapshot(AwsSnapshot),
    NativeDevices(HashMap<String, Option<String>>),
}

impl From<VolumeSource> for VolumeSourceExt {
    fn from(volume_source: VolumeSource) -> Self {
        match volume_source {
            VolumeSource::AwsSnapshot(snapshot) => Self::AwsSnapshot(snapshot),
        }
    }
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct AwsSnapshot {
    pub realm: String,
    pub region: String,
    pub id: String,
}

impl Default for ReplicaCreateRealm {
    fn default() -> Self {
        Self::Aws(AwsReplicaCreateRealm {
            realm: String::default(),
            region: String::default(),
            subnet: String::default(),
        })
    }
}

#[skip_serializing_none]
#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct ReplicaCreate {
    #[serde(default)]
    pub name: String,
    #[serde(default)]
    pub description: Option<String>,
    #[serde(flatten)]
    pub realm: ReplicaCreateRealm,
    pub iscsi_portal: Option<String>,
    #[serde(default, skip_serializing_if = "Not::not")]
    pub primary: bool,
    // INTERNAL
    pub private_link: Option<PrivateLinkNlb>,
}

#[derive(Default, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct AwsReplicaCreate {
    #[serde(default)]
    pub name: String,
    #[serde(default)]
    pub description: Option<String>,
    #[serde(flatten)]
    pub realm: AwsReplicaCreateRealm,
    pub iscsi_portal: Option<String>,
    #[serde(default)]
    pub primary: bool,
}

#[derive(Default, Clone, Debug, Deserialize, Serialize)]
pub struct AzureReplicaCreate {
    #[serde(default)]
    pub name: String,
    #[serde(default)]
    pub description: Option<String>,
    #[serde(flatten)]
    pub realm: AzureReplicaCreateRealm,
    pub iscsi_portal: Option<String>,
    #[serde(default)]
    pub primary: bool,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ReplicaCreateRealm {
    Aws(AwsReplicaCreateRealm),
    Azure(AzureReplicaCreateRealm),
}

#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub struct AwsReplicaCreateRealm {
    pub realm: String,
    pub region: String,
    pub subnet: String,
}

#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub struct AzureReplicaCreateRealm {
    pub realm: String,
    pub resource_group: String,
    pub subnet: String,
}

impl ReplicaCreateRealm {
    pub fn vendor(&self) -> &str {
        match self {
            Self::Aws { .. } => "aws",
            Self::Azure { .. } => "azure",
        }
    }

    pub fn realm(&self) -> String {
        match self {
            Self::Aws(AwsReplicaCreateRealm { realm, .. }) => realm.clone(),
            Self::Azure(AzureReplicaCreateRealm { realm, .. }) => realm.clone(),
        }
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Volume {
    pub name: String,
    pub id: Uuid,
    pub description: String,
    pub size_gb: u64,
    pub source: Option<VolumeSource>,
    pub replicas: Vec<Replica>,
    pub iqn: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub job: Option<Uuid>,
    pub created: Option<DateTime<Utc>>,
    pub modified: Option<DateTime<Utc>>,
    pub status: Status,
}

#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct Status {
    pub value: String,
    pub message: Option<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Replica {
    #[serde(default)]
    pub name: String,
    pub description: String,
    #[serde(flatten)]
    pub realm: ReplicaRealm,
    #[serde(default)]
    pub primary: bool,
    pub iscsi_portal: Option<String>,
    #[serde(default)]
    pub status: Status,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ReplicaRealm {
    Aws {
        realm: String,
        region: String,
        subnet: String,
    },
    Azure {
        realm: String,
        resource_group: String,
        subnet: String,
    },
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct Snapshot {
    pub id: String,
    pub name: String,
    pub created: Option<DateTime<Utc>>,
    #[serde(default)]
    pub status: Status,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SnapshotCreate {
    pub name: Option<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SnapshotExport {
    pub id: String,
    pub status: Status,
    #[serde(flatten)]
    pub vendor_export: VendorSnapshotExport,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum VendorSnapshotExport {
    Aws(AwsSnapshotExport),
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AwsSnapshotExport {
    pub realm: String,
    pub region: String,
    pub id: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SnapshotExportCreate {
    Aws(AwsSnapshotExportCreate),
}

impl Default for SnapshotExportCreate {
    fn default() -> Self {
        Self::Aws(AwsSnapshotExportCreate::default())
    }
}

#[derive(Clone, Default, Debug, Deserialize, Serialize)]
pub struct AwsSnapshotExportCreate {
    pub realm: String,
    pub region: String,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum StatusValue {
    Uninitialized,
    Provisioning,
    Starting,
    Syncing,
    Stopping,
    Deleting,
    Reconfiguring,
    Online,
    Offline,
    Degraded,
    Error,
}

impl Default for StatusValue {
    fn default() -> Self {
        Self::Uninitialized
    }
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct JobStatus {
    pub value: JobStatusValue,
    pub message: Option<String>,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum JobStatusValue {
    Pending,
    InProgress,
    Done,
    Error,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub struct Job {
    pub parent: Option<Uuid>,
    pub id: Uuid,
    pub r#type: String,
    pub created: DateTime<Utc>,
    pub modified: DateTime<Utc>,
    pub status: Status,
    pub details: Map<String, Value>,
    pub children: usize,
    pub sub_jobs: Vec<Job>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct JobAccepted {
    pub job_id: Uuid,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Error {
    pub http_code: u16,
    pub http_status: String,
    pub error: Option<serde_json::Value>,
    pub message: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum WsEvent {
    Job(Job),
    Volume(Volume),
}

#[derive(Clone, Default, Debug, Deserialize, Serialize)]
pub struct QueryParams {
    #[serde(default)]
    include: String,
}

impl QueryParams {
    pub fn include(&self, field: &str) -> bool {
        self.include.split(',').any(|f| f == field)
    }

    pub fn include_job(&self) -> bool {
        self.include("job")
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Version {
    pub release: String,
    pub name: String,
    pub version: String,
    pub build: String,
    pub profile: String,
    pub allocator: String,
    pub branch: String,
    pub tag: String,
    pub commit: String,
    pub extra: String,
    pub metadata: Vec<String>,
}