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
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.

use std::path::PathBuf;
use std::sync::Arc;

use clap::ArgEnum;
use mz_aws_secrets_controller::AwsSecretsClient;
use mz_orchestrator_kubernetes::secrets::KubernetesSecretsReader;
use mz_orchestrator_process::secrets::ProcessSecretsReader;
use mz_secrets::SecretsReader;

#[derive(clap::Parser, Clone, Debug)]
pub struct SecretsReaderCliArgs {
    /// The secrets reader implementation to use.
    #[structopt(long, arg_enum, env = "SECRETS_READER")]
    pub secrets_reader: SecretsControllerKind,
    /// When using the process secrets reader, the directory on the filesystem
    /// where secrets are stored.
    #[structopt(
        long,
        required_if_eq("secrets-reader", "local-file"),
        env = "SECRETS_READER_LOCAL_FILE_DIR"
    )]
    pub secrets_reader_local_file_dir: Option<PathBuf>,
    /// When using the Kubernetes secrets reader, the Kubernetes context to
    /// load.
    #[structopt(
        long,
        required_if_eq("secrets-reader", "kubernetes"),
        env = "SECRETS_READER_KUBERNETES_CONTEXT"
    )]
    pub secrets_reader_kubernetes_context: Option<String>,
    /// When using the AWS secrets reader, we need both of the following.
    #[structopt(
        long,
        required_if_eq("secrets-reader", "aws-secrets-manager"),
        env = "SECRETS_READER_AWS_REGION"
    )]
    pub secrets_reader_aws_region: Option<String>,
    #[structopt(
        long,
        required_if_eq("secrets-reader", "aws-secrets-manager"),
        env = "SECRETS_READER_AWS_PREFIX"
    )]
    pub secrets_reader_aws_prefix: Option<String>,
}

#[derive(ArgEnum, Debug, Clone, Copy)]
pub enum SecretsControllerKind {
    LocalFile,
    Kubernetes,
    AwsSecretsManager,
}

impl SecretsReaderCliArgs {
    /// Loads the secrets reader specified by the command-line arguments.
    pub async fn load(self) -> Result<Arc<dyn SecretsReader>, anyhow::Error> {
        match self.secrets_reader {
            SecretsControllerKind::LocalFile => {
                let dir = self.secrets_reader_local_file_dir.expect("clap enforced");
                Ok(Arc::new(ProcessSecretsReader::new(dir)))
            }
            SecretsControllerKind::Kubernetes => {
                let context = self
                    .secrets_reader_kubernetes_context
                    .expect("clap enforced");
                Ok(Arc::new(KubernetesSecretsReader::new(context).await?))
            }
            SecretsControllerKind::AwsSecretsManager => {
                let region = self.secrets_reader_aws_region.expect("clap enforced");
                let prefix = self.secrets_reader_aws_prefix.expect("clap enforced");
                Ok(Arc::new(AwsSecretsClient::new(&region, &prefix).await))
            }
        }
    }

    /// Turn this struct back into arguments. Useful for passing through to other services.
    ///
    /// Expects the correct arguments to be filled in, based on the `clap` requirements.
    pub fn to_flags(&self) -> Vec<String> {
        match self.secrets_reader {
            SecretsControllerKind::LocalFile => {
                vec![
                    "--secrets-reader=local-file".to_string(),
                    format!(
                        "--secrets-reader-local-file-dir={}",
                        self.secrets_reader_local_file_dir
                            .as_ref()
                            .expect("initialized correctly")
                            .display()
                    ),
                ]
            }
            SecretsControllerKind::Kubernetes => {
                vec![
                    "--secrets-reader=kubernetes".to_string(),
                    format!(
                        "--secrets-reader-kubernetes-context={}",
                        self.secrets_reader_kubernetes_context
                            .as_ref()
                            .expect("initialized correctly")
                    ),
                ]
            }
            SecretsControllerKind::AwsSecretsManager => {
                vec![
                    "--secrets-reader=aws-secrets-manager".to_string(),
                    format!(
                        "--secrets-reader-aws-region={}",
                        self.secrets_reader_aws_region
                            .as_ref()
                            .expect("initialized correctly")
                    ),
                    format!(
                        "--secrets-reader-aws-prefix={}",
                        self.secrets_reader_aws_prefix
                            .as_ref()
                            .expect("initialized correctly")
                    ),
                ]
            }
        }
    }
}