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
use super::{vault::Vault, Manifest, Region, Result};

/// Type of primary workload that is associated with the Manifest
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum PrimaryWorkload {
    Deployment,
    Statefulset,
}

impl ToString for PrimaryWorkload {
    fn to_string(&self) -> String {
        format!("{:?}", self).to_lowercase()
    }
}

impl Default for PrimaryWorkload {
    fn default() -> Self {
        Self::Deployment
    }
}

/// Various internal states a manifest can exist in depending on resolution.
///
/// This only matters within shipcat and is used to optimize speed of accessors.
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub enum ManifestState {
    /// A completed manifest
    ///
    /// The version that is fully ready to pass to `helm template`, i.e.:
    /// - region overrides accounted for
    /// - evars are templated
    /// - secrets are available
    /// - configs inlined and templated with secrets
    ///
    /// This is the `shipcat values -s` equivalent of a manifest.
    ///
    /// A `Base` Manifest can become a `Completed` Manifest by:
    /// - templating evars
    /// - evaluating secrets
    /// - templating configs
    Completed,

    /// A stubbed manifest
    ///
    /// Indistinguishable from a `Completed` manifest except from secrets:
    /// - secrets populated with garbage values (not resolved from vault)
    /// - configs templated with garbage secret values
    /// - evars templated with garbage secret values
    ///
    /// This is the `shipcat values` equivalent of a manifest.
    Stubbed,

    /// The Base manifest
    ///
    /// A state that is upgradeable to a completed one, contains all the pieces,
    /// but does not have any secrets. This form is used internally in the cli,
    /// as well as in the cluster as the canonical CRD state.
    ///
    /// Major features:
    /// - region overrides accounted for
    /// - templates (configs + evars) left in template form
    /// - secrets unresolved
    ///
    /// This is the CRD equivalent of a manifest.
    /// It's important that the CRD equivalent abstracts away config files, but not secrets.
    /// Thus files have to be read, and not templated for this, then shipped off to kube.
    Base,
}

/// Default is the feature-specified base type to force constructors into chosing.
///
/// This relies on serde default to populate on deserialize from disk/crd.
impl Default for ManifestState {
    fn default() -> Self {
        ManifestState::Base
    }
}

/// This library defines the way to upgrade a manifest from Base
/// but each backend has to implement its own way of:
/// - listing services from its backing
/// - creating a base manifest from its backing
impl Manifest {
    /// Upgrade a `Base` manifest to either a Complete or a Stubbed one
    async fn upgrade(mut self, reg: &Region, state: ManifestState) -> Result<Self> {
        assert_eq!(self.state, ManifestState::Base); // sanity
        let v = match state {
            ManifestState::Completed => Vault::regional(&reg.vault)?,
            ManifestState::Stubbed => Vault::mocked(&reg.vault)?,
            _ => bail!("Can only upgrade a Base manifest to Completed or Stubbed"),
        };
        // replace one-off templates in evar strings with values
        // note that this happens before secrets because:
        // secrets may be injected at this step from the Region
        self.template_evars(reg)?;
        // secrets before configs (.j2 template files use raw secret values)
        self.secrets(&v, &reg.vault).await?;

        // templates last
        self.template_configs(reg)?;
        self.state = state;
        Ok(self)
    }

    /// Complete a Base manifest with stub secrets
    pub async fn stub(self, reg: &Region) -> Result<Self> {
        self.upgrade(reg, ManifestState::Stubbed).await
    }

    /// Complete a Base manifest with actual secrets
    pub async fn complete(self, reg: &Region) -> Result<Self> {
        self.upgrade(reg, ManifestState::Completed).await
    }

    /// Check to see we are using the right types of manifests internally
    pub fn is_base(&self) -> bool {
        self.state == ManifestState::Base
    }
}

/// Various states a Config can exist in depending on resolution.
///
/// Within shipcat, this is used to optimize speed of accessors.
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub enum ConfigState {
    /// A filtered config for a specific region, with resolved secrets
    Filtered,

    /// Region-independent, unresolved secrets
    ///
    /// Just like Base - but for all regions
    UnionisedBase,

    /// A config with a single region entry with blank secrets
    ///
    /// Same as what's on disk, secrets unresolved, but only one region.
    /// This is the CRD equivalent.
    Base,

    /// The full config as read from disk. Secrets unresolved
    #[cfg(feature = "filesystem")]
    File,
}

/// Default is the feature-specified base type to force constructors into chosing.
///
/// This relies on serde default to populate on deserialize from disk/crd.
impl Default for ConfigState {
    #[cfg(feature = "filesystem")]
    fn default() -> Self {
        ConfigState::File
    }

    #[cfg(not(feature = "filesystem"))]
    fn default() -> Self {
        ConfigState::Base
    }
}