1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
//! Define the tenant details.
//!
use crate::client::ObjectstoreClient;
use crate::response::get_content_text;
use anyhow::{bail, Context as _, Result};
use derive_builder::Builder;
use reqwest::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE};
use serde::{Deserialize, Serialize};
use serde_aux::field_attributes::deserialize_default_from_null;

/// A tenant is a logical construct resulting from the binding of an account to an object store.
#[derive(Builder, Clone, Debug, Default, Deserialize, Serialize)]
#[builder(setter(skip))]
#[serde(
    rename_all(serialize = "snake_case", deserialize = "camelCase"),
    rename(serialize = "tenant_create")
)]
pub struct Tenant {
    /// Name assigned to this resource in ECS. The resource name is set by a user and can be changed at any time. It is not a unique identifier.
    #[serde(deserialize_with = "deserialize_default_from_null")]
    pub name: String,
    /// Identifier that is generated by ECS when the resource is created. The resource Id is guaranteed to be unique and immutable across all virtual data centers for all time.
    #[builder(setter(into))]
    #[serde(rename(serialize = "account_id"))]
    pub id: String,
    /// Hyperlink to the details for this resource
    #[serde(deserialize_with = "deserialize_default_from_null")]
    pub link: String,
    /// Timestamp that shows when this resource was created in ECS
    #[serde(deserialize_with = "deserialize_default_from_null")]
    pub creation_time: String,
    /// Indicates whether the resource is inactive. When a user removes a resource, the resource is put in this state before it is removed from the ECS database.
    #[serde(deserialize_with = "deserialize_default_from_null")]
    pub inactive: bool,
    /// Indicates whether the resource is global.
    #[serde(deserialize_with = "deserialize_default_from_null")]
    pub global: bool,
    /// Indicates whether the resource is remote.
    #[serde(deserialize_with = "deserialize_default_from_null")]
    pub remote: bool,
    //pub vdc: String,
    /// Indicated whether the resource is an internal resource
    #[serde(deserialize_with = "deserialize_default_from_null")]
    pub internal: bool,
    pub tenant_default_vpool: String,
    /// tag to enable encryption for the tenant
    #[builder(setter(skip = false), default = "false")]
    pub is_encryption_enabled: bool,
    /// Default bucket quota size.
    #[builder(setter(skip = false), default)]
    pub default_bucket_block_size: i64,
    /// Tag to enable compliance compliance
    #[builder(setter(skip = false), default = "false")]
    pub is_compliance_enabled: bool,
    pub hard_quota_in_g_b: i64,
    pub soft_quota_in_g_b: i64,
    pub hard_quota_in_count: i64,
    pub soft_quota_in_count: i64,
    //pub retention_classes: RetentionClasses,
    /// Alias of tenant
    #[builder(setter(into))]
    pub alias: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(
    rename_all(serialize = "snake_case", deserialize = "camelCase"),
    rename(serialize = "tenant_update")
)]
struct TenantUpdate {
    pub alias: String,
    pub default_bucket_block_size: i64,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ListTenantsResponse {
    pub tenant: Vec<Tenant>,
    #[serde(rename = "Filter")]
    pub filter: String,
    #[serde(rename = "NextMarker")]
    pub next_marker: Option<String>,
    #[serde(rename = "MaxTenants")]
    pub max_tenants: Option<u32>,
    pub next_page_link: Option<String>,
}

impl Tenant {
    pub(crate) fn create(client: &mut ObjectstoreClient, tenant: Tenant) -> Result<Tenant> {
        let request_url = format!("{}object/tenants/tenant", client.endpoint,);
        let body = quick_xml::se::to_string(&tenant)?;
        let resp = client
            .management_client
            .http_client
            .post(request_url)
            .header(ACCEPT, "application/json")
            .header(
                AUTHORIZATION,
                client.management_client.access_token.as_ref().unwrap(),
            )
            .header(CONTENT_TYPE, "application/xml")
            .header("X-EMC-Override", "true")
            .body(body)
            .send()?;
        let text = get_content_text(resp)?;
        let resp: Tenant = serde_json::from_str(&text).with_context(|| {
            format!("Unable to deserialise CreateTenant. Body was: \"{}\"", text)
        })?;
        Ok(resp)
    }

    pub(crate) fn update(client: &mut ObjectstoreClient, tenant: Tenant) -> Result<()> {
        let request_url = format!("{}object/tenants/tenant/{}", client.endpoint, tenant.id);
        let tenant_update = TenantUpdate {
            alias: tenant.alias,
            default_bucket_block_size: tenant.default_bucket_block_size,
        };
        let body = quick_xml::se::to_string(&tenant_update)?;
        let resp = client
            .management_client
            .http_client
            .put(request_url)
            .header(ACCEPT, "application/json")
            .header(
                AUTHORIZATION,
                client.management_client.access_token.as_ref().unwrap(),
            )
            .header(CONTENT_TYPE, "application/xml")
            .header("X-EMC-Override", "true")
            .body(body)
            .send()?;
        if !resp.status().is_success() {
            bail!("Request failed: {}", resp.text()?);
        }
        Ok(())
    }

    pub(crate) fn get(client: &mut ObjectstoreClient, name: &str) -> Result<Tenant> {
        let request_url = format!("{}object/tenants/tenant/{}", client.endpoint, name,);
        let resp = client
            .management_client
            .http_client
            .get(request_url)
            .header(ACCEPT, "application/json")
            .header(
                AUTHORIZATION,
                client.management_client.access_token.as_ref().unwrap(),
            )
            .send()?;
        let text = get_content_text(resp)?;
        let resp: Tenant = serde_json::from_str(&text)
            .with_context(|| format!("Unable to deserialise GetTenant. Body was: \"{}\"", text))?;
        Ok(resp)
    }

    pub(crate) fn delete(client: &mut ObjectstoreClient, name: &str) -> Result<()> {
        let request_url = format!("{}object/tenants/tenant/{}/delete", client.endpoint, name,);
        let resp = client
            .management_client
            .http_client
            .post(request_url)
            .header(ACCEPT, "application/json")
            .header(
                AUTHORIZATION,
                client.management_client.access_token.as_ref().unwrap(),
            )
            .header("X-EMC-Override", "true")
            .send()?;
        if !resp.status().is_success() {
            bail!("Request failed: {}", resp.text()?);
        }
        Ok(())
    }

    pub(crate) fn list(client: &mut ObjectstoreClient, name_prefix: &str) -> Result<Vec<Tenant>> {
        let request_url = format!("{}object/tenants", client.endpoint,);
        let mut req = client
            .management_client
            .http_client
            .get(request_url)
            .header(ACCEPT, "application/json")
            .header(
                AUTHORIZATION,
                client.management_client.access_token.as_ref().unwrap(),
            );
        if !name_prefix.is_empty() {
            req = req.query(&[("name", name_prefix)]);
        }
        let response = req.send()?;
        let text = get_content_text(response)?;
        let mut resp: ListTenantsResponse = serde_json::from_str(&text).with_context(|| {
            format!(
                "Unable to deserialise ListTenantsResponse. Body was: \"{}\"",
                text
            )
        })?;
        let mut tenants: Vec<Tenant> = vec![];
        tenants.extend(resp.tenant);
        while let Some(marker) = resp.next_marker {
            let request_url = format!("{}object/tenants?marker={}", client.endpoint, marker,);
            let mut req = client
                .management_client
                .http_client
                .get(request_url)
                .header(ACCEPT, "application/json")
                .header(
                    AUTHORIZATION,
                    client.management_client.access_token.as_ref().unwrap(),
                );
            if !name_prefix.is_empty() {
                req = req.query(&[("name", name_prefix)]);
            }
            let response = req.send()?;
            let text = get_content_text(response)?;
            resp = serde_json::from_str(&text).with_context(|| {
                format!(
                    "Unable to deserialise ListTenantsResponse. Body was: \"{}\"",
                    text
                )
            })?;
            tenants.extend(resp.tenant);
        }
        Ok(tenants)
    }
}