package command import ( "os" "os/signal" "syscall" "github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/builtin/plugin" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/physical" "github.com/hashicorp/vault/sdk/version" "github.com/mitchellh/cli" /* The builtinplugins package is initialized here because it, in turn, initializes the database plugins. They register multiple database drivers for the "database/sql" package. */ _ "github.com/hashicorp/vault/helper/builtinplugins" auditFile "github.com/hashicorp/vault/builtin/audit/file" auditSocket "github.com/hashicorp/vault/builtin/audit/socket" auditSyslog "github.com/hashicorp/vault/builtin/audit/syslog" credAliCloud "github.com/hashicorp/vault-plugin-auth-alicloud" credCentrify "github.com/hashicorp/vault-plugin-auth-centrify" credCF "github.com/hashicorp/vault-plugin-auth-cf" credGcp "github.com/hashicorp/vault-plugin-auth-gcp/plugin" credOIDC "github.com/hashicorp/vault-plugin-auth-jwt" credKerb "github.com/hashicorp/vault-plugin-auth-kerberos" credOCI "github.com/hashicorp/vault-plugin-auth-oci" credAws "github.com/hashicorp/vault/builtin/credential/aws" credCert "github.com/hashicorp/vault/builtin/credential/cert" credGitHub "github.com/hashicorp/vault/builtin/credential/github" credLdap "github.com/hashicorp/vault/builtin/credential/ldap" credOkta "github.com/hashicorp/vault/builtin/credential/okta" credToken "github.com/hashicorp/vault/builtin/credential/token" credUserpass "github.com/hashicorp/vault/builtin/credential/userpass" logicalKv "github.com/hashicorp/vault-plugin-secrets-kv" logicalDb "github.com/hashicorp/vault/builtin/logical/database" physAerospike "github.com/hashicorp/vault/physical/aerospike" physAliCloudOSS "github.com/hashicorp/vault/physical/alicloudoss" physAzure "github.com/hashicorp/vault/physical/azure" physCassandra "github.com/hashicorp/vault/physical/cassandra" physCockroachDB "github.com/hashicorp/vault/physical/cockroachdb" physConsul "github.com/hashicorp/vault/physical/consul" physCouchDB "github.com/hashicorp/vault/physical/couchdb" physDynamoDB "github.com/hashicorp/vault/physical/dynamodb" physEtcd "github.com/hashicorp/vault/physical/etcd" physFoundationDB "github.com/hashicorp/vault/physical/foundationdb" physGCS "github.com/hashicorp/vault/physical/gcs" physManta "github.com/hashicorp/vault/physical/manta" physMSSQL "github.com/hashicorp/vault/physical/mssql" physMySQL "github.com/hashicorp/vault/physical/mysql" physOCI "github.com/hashicorp/vault/physical/oci" physPostgreSQL "github.com/hashicorp/vault/physical/postgresql" physRaft "github.com/hashicorp/vault/physical/raft" physS3 "github.com/hashicorp/vault/physical/s3" physSpanner "github.com/hashicorp/vault/physical/spanner" physSwift "github.com/hashicorp/vault/physical/swift" physZooKeeper "github.com/hashicorp/vault/physical/zookeeper" physFile "github.com/hashicorp/vault/sdk/physical/file" physInmem "github.com/hashicorp/vault/sdk/physical/inmem" sr "github.com/hashicorp/vault/serviceregistration" csr "github.com/hashicorp/vault/serviceregistration/consul" ksr "github.com/hashicorp/vault/serviceregistration/kubernetes" ) const ( // EnvVaultCLINoColor is an env var that toggles colored UI output. EnvVaultCLINoColor = `VAULT_CLI_NO_COLOR` // EnvVaultFormat is the output format EnvVaultFormat = `VAULT_FORMAT` // EnvVaultLicense is an env var used in Vault Enterprise to provide a license blob EnvVaultLicense = "VAULT_LICENSE" // EnvVaultLicensePath is an env var used in Vault Enterprise to provide a // path to a license file on disk EnvVaultLicensePath = "VAULT_LICENSE_PATH" // DisableSSCTokens is an env var used to disable index bearing // token functionality DisableSSCTokens = "VAULT_DISABLE_SERVER_SIDE_CONSISTENT_TOKENS" // flagNameAddress is the flag used in the base command to read in the // address of the Vault server. flagNameAddress = "address" // flagnameCACert is the flag used in the base command to read in the CA // cert. flagNameCACert = "ca-cert" // flagnameCAPath is the flag used in the base command to read in the CA // cert path. flagNameCAPath = "ca-path" // flagNameClientCert is the flag used in the base command to read in the // client key flagNameClientKey = "client-key" // flagNameClientCert is the flag used in the base command to read in the // client cert flagNameClientCert = "client-cert" // flagNameTLSSkipVerify is the flag used in the base command to read in // the option to ignore TLS certificate verification. flagNameTLSSkipVerify = "tls-skip-verify" // flagTLSServerName is the flag used in the base command to read in // the TLS server name. flagTLSServerName = "tls-server-name" // flagNameAuditNonHMACRequestKeys is the flag name used for auth/secrets enable flagNameAuditNonHMACRequestKeys = "audit-non-hmac-request-keys" // flagNameAuditNonHMACResponseKeys is the flag name used for auth/secrets enable flagNameAuditNonHMACResponseKeys = "audit-non-hmac-response-keys" // flagNameDescription is the flag name used for tuning the secret and auth mount description parameter flagNameDescription = "description" // flagListingVisibility is the flag to toggle whether to show the mount in the UI-specific listing endpoint flagNameListingVisibility = "listing-visibility" // flagNamePassthroughRequestHeaders is the flag name used to set passthrough request headers to the backend flagNamePassthroughRequestHeaders = "passthrough-request-headers" // flagNameAllowedResponseHeaders is used to set allowed response headers from a plugin flagNameAllowedResponseHeaders = "allowed-response-headers" // flagNameTokenType is the flag name used to force a specific token type flagNameTokenType = "token-type" // flagNameAllowedManagedKeys is the flag name used for auth/secrets enable flagNameAllowedManagedKeys = "allowed-managed-keys" ) var ( auditBackends = map[string]audit.Factory{ "file": auditFile.Factory, "socket": auditSocket.Factory, "syslog": auditSyslog.Factory, } credentialBackends = map[string]logical.Factory{ "plugin": plugin.Factory, } logicalBackends = map[string]logical.Factory{ "plugin": plugin.Factory, "database": logicalDb.Factory, // This is also available in the plugin catalog, but is here due to the need to // automatically mount it. "kv": logicalKv.Factory, } physicalBackends = map[string]physical.Factory{ "aerospike": physAerospike.NewAerospikeBackend, "alicloudoss": physAliCloudOSS.NewAliCloudOSSBackend, "azure": physAzure.NewAzureBackend, "cassandra": physCassandra.NewCassandraBackend, "cockroachdb": physCockroachDB.NewCockroachDBBackend, "consul": physConsul.NewConsulBackend, "couchdb_transactional": physCouchDB.NewTransactionalCouchDBBackend, "couchdb": physCouchDB.NewCouchDBBackend, "dynamodb": physDynamoDB.NewDynamoDBBackend, "etcd": physEtcd.NewEtcdBackend, "file_transactional": physFile.NewTransactionalFileBackend, "file": physFile.NewFileBackend, "foundationdb": physFoundationDB.NewFDBBackend, "gcs": physGCS.NewBackend, "inmem_ha": physInmem.NewInmemHA, "inmem_transactional_ha": physInmem.NewTransactionalInmemHA, "inmem_transactional": physInmem.NewTransactionalInmem, "inmem": physInmem.NewInmem, "manta": physManta.NewMantaBackend, "mssql": physMSSQL.NewMSSQLBackend, "mysql": physMySQL.NewMySQLBackend, "oci": physOCI.NewBackend, "postgresql": physPostgreSQL.NewPostgreSQLBackend, "s3": physS3.NewS3Backend, "spanner": physSpanner.NewBackend, "swift": physSwift.NewSwiftBackend, "raft": physRaft.NewRaftBackend, "zookeeper": physZooKeeper.NewZooKeeperBackend, } serviceRegistrations = map[string]sr.Factory{ "consul": csr.NewServiceRegistration, "kubernetes": ksr.NewServiceRegistration, } initCommandsEnt = func(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {} ) // Commands is the mapping of all the available commands. var Commands map[string]cli.CommandFactory func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { loginHandlers := map[string]LoginHandler{ "alicloud": &credAliCloud.CLIHandler{}, "aws": &credAws.CLIHandler{}, "centrify": &credCentrify.CLIHandler{}, "cert": &credCert.CLIHandler{}, "cf": &credCF.CLIHandler{}, "gcp": &credGcp.CLIHandler{}, "github": &credGitHub.CLIHandler{}, "kerberos": &credKerb.CLIHandler{}, "ldap": &credLdap.CLIHandler{}, "oci": &credOCI.CLIHandler{}, "oidc": &credOIDC.CLIHandler{}, "okta": &credOkta.CLIHandler{}, "pcf": &credCF.CLIHandler{}, // Deprecated. "radius": &credUserpass.CLIHandler{ DefaultMount: "radius", }, "token": &credToken.CLIHandler{}, "userpass": &credUserpass.CLIHandler{ DefaultMount: "userpass", }, } getBaseCommand := func() *BaseCommand { return &BaseCommand{ UI: ui, tokenHelper: runOpts.TokenHelper, flagAddress: runOpts.Address, client: runOpts.Client, } } Commands = map[string]cli.CommandFactory{ "agent": func() (cli.Command, error) { return &AgentCommand{ BaseCommand: &BaseCommand{ UI: serverCmdUi, }, ShutdownCh: MakeShutdownCh(), }, nil }, "audit": func() (cli.Command, error) { return &AuditCommand{ BaseCommand: getBaseCommand(), }, nil }, "audit disable": func() (cli.Command, error) { return &AuditDisableCommand{ BaseCommand: getBaseCommand(), }, nil }, "audit enable": func() (cli.Command, error) { return &AuditEnableCommand{ BaseCommand: getBaseCommand(), }, nil }, "audit list": func() (cli.Command, error) { return &AuditListCommand{ BaseCommand: getBaseCommand(), }, nil }, "auth tune": func() (cli.Command, error) { return &AuthTuneCommand{ BaseCommand: getBaseCommand(), }, nil }, "auth": func() (cli.Command, error) { return &AuthCommand{ BaseCommand: getBaseCommand(), }, nil }, "auth disable": func() (cli.Command, error) { return &AuthDisableCommand{ BaseCommand: getBaseCommand(), }, nil }, "auth enable": func() (cli.Command, error) { return &AuthEnableCommand{ BaseCommand: getBaseCommand(), }, nil }, "auth help": func() (cli.Command, error) { return &AuthHelpCommand{ BaseCommand: getBaseCommand(), Handlers: loginHandlers, }, nil }, "auth list": func() (cli.Command, error) { return &AuthListCommand{ BaseCommand: getBaseCommand(), }, nil }, "auth move": func() (cli.Command, error) { return &AuthMoveCommand{ BaseCommand: getBaseCommand(), }, nil }, "debug": func() (cli.Command, error) { return &DebugCommand{ BaseCommand: getBaseCommand(), ShutdownCh: MakeShutdownCh(), }, nil }, "delete": func() (cli.Command, error) { return &DeleteCommand{ BaseCommand: getBaseCommand(), }, nil }, "lease": func() (cli.Command, error) { return &LeaseCommand{ BaseCommand: getBaseCommand(), }, nil }, "lease renew": func() (cli.Command, error) { return &LeaseRenewCommand{ BaseCommand: getBaseCommand(), }, nil }, "lease lookup": func() (cli.Command, error) { return &LeaseLookupCommand{ BaseCommand: getBaseCommand(), }, nil }, "lease revoke": func() (cli.Command, error) { return &LeaseRevokeCommand{ BaseCommand: getBaseCommand(), }, nil }, "list": func() (cli.Command, error) { return &ListCommand{ BaseCommand: getBaseCommand(), }, nil }, "login": func() (cli.Command, error) { return &LoginCommand{ BaseCommand: getBaseCommand(), Handlers: loginHandlers, }, nil }, "namespace": func() (cli.Command, error) { return &NamespaceCommand{ BaseCommand: getBaseCommand(), }, nil }, "namespace list": func() (cli.Command, error) { return &NamespaceListCommand{ BaseCommand: getBaseCommand(), }, nil }, "namespace lookup": func() (cli.Command, error) { return &NamespaceLookupCommand{ BaseCommand: getBaseCommand(), }, nil }, "namespace create": func() (cli.Command, error) { return &NamespaceCreateCommand{ BaseCommand: getBaseCommand(), }, nil }, "namespace delete": func() (cli.Command, error) { return &NamespaceDeleteCommand{ BaseCommand: getBaseCommand(), }, nil }, "namespace lock": func() (cli.Command, error) { return &NamespaceAPILockCommand{ BaseCommand: getBaseCommand(), }, nil }, "namespace unlock": func() (cli.Command, error) { return &NamespaceAPIUnlockCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator": func() (cli.Command, error) { return &OperatorCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator diagnose": func() (cli.Command, error) { return &OperatorDiagnoseCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator generate-root": func() (cli.Command, error) { return &OperatorGenerateRootCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator init": func() (cli.Command, error) { return &OperatorInitCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator key-status": func() (cli.Command, error) { return &OperatorKeyStatusCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator migrate": func() (cli.Command, error) { return &OperatorMigrateCommand{ BaseCommand: getBaseCommand(), PhysicalBackends: physicalBackends, ShutdownCh: MakeShutdownCh(), }, nil }, "operator raft": func() (cli.Command, error) { return &OperatorRaftCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator raft autopilot get-config": func() (cli.Command, error) { return &OperatorRaftAutopilotGetConfigCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator raft autopilot set-config": func() (cli.Command, error) { return &OperatorRaftAutopilotSetConfigCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator raft autopilot state": func() (cli.Command, error) { return &OperatorRaftAutopilotStateCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator raft list-peers": func() (cli.Command, error) { return &OperatorRaftListPeersCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator raft join": func() (cli.Command, error) { return &OperatorRaftJoinCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator raft remove-peer": func() (cli.Command, error) { return &OperatorRaftRemovePeerCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator raft snapshot": func() (cli.Command, error) { return &OperatorRaftSnapshotCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator raft snapshot restore": func() (cli.Command, error) { return &OperatorRaftSnapshotRestoreCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator raft snapshot save": func() (cli.Command, error) { return &OperatorRaftSnapshotSaveCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator rekey": func() (cli.Command, error) { return &OperatorRekeyCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator rotate": func() (cli.Command, error) { return &OperatorRotateCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator seal": func() (cli.Command, error) { return &OperatorSealCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator step-down": func() (cli.Command, error) { return &OperatorStepDownCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator usage": func() (cli.Command, error) { return &OperatorUsageCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator unseal": func() (cli.Command, error) { return &OperatorUnsealCommand{ BaseCommand: getBaseCommand(), }, nil }, "operator members": func() (cli.Command, error) { return &OperatorMembersCommand{ BaseCommand: getBaseCommand(), }, nil }, "path-help": func() (cli.Command, error) { return &PathHelpCommand{ BaseCommand: getBaseCommand(), }, nil }, "plugin": func() (cli.Command, error) { return &PluginCommand{ BaseCommand: getBaseCommand(), }, nil }, "plugin deregister": func() (cli.Command, error) { return &PluginDeregisterCommand{ BaseCommand: getBaseCommand(), }, nil }, "plugin info": func() (cli.Command, error) { return &PluginInfoCommand{ BaseCommand: getBaseCommand(), }, nil }, "plugin list": func() (cli.Command, error) { return &PluginListCommand{ BaseCommand: getBaseCommand(), }, nil }, "plugin register": func() (cli.Command, error) { return &PluginRegisterCommand{ BaseCommand: getBaseCommand(), }, nil }, "plugin reload": func() (cli.Command, error) { return &PluginReloadCommand{ BaseCommand: getBaseCommand(), }, nil }, "plugin reload-status": func() (cli.Command, error) { return &PluginReloadStatusCommand{ BaseCommand: getBaseCommand(), }, nil }, "policy": func() (cli.Command, error) { return &PolicyCommand{ BaseCommand: getBaseCommand(), }, nil }, "policy delete": func() (cli.Command, error) { return &PolicyDeleteCommand{ BaseCommand: getBaseCommand(), }, nil }, "policy fmt": func() (cli.Command, error) { return &PolicyFmtCommand{ BaseCommand: getBaseCommand(), }, nil }, "policy list": func() (cli.Command, error) { return &PolicyListCommand{ BaseCommand: getBaseCommand(), }, nil }, "policy read": func() (cli.Command, error) { return &PolicyReadCommand{ BaseCommand: getBaseCommand(), }, nil }, "policy write": func() (cli.Command, error) { return &PolicyWriteCommand{ BaseCommand: getBaseCommand(), }, nil }, "print": func() (cli.Command, error) { return &PrintCommand{ BaseCommand: getBaseCommand(), }, nil }, "print token": func() (cli.Command, error) { return &PrintTokenCommand{ BaseCommand: getBaseCommand(), }, nil }, "read": func() (cli.Command, error) { return &ReadCommand{ BaseCommand: getBaseCommand(), }, nil }, "secrets": func() (cli.Command, error) { return &SecretsCommand{ BaseCommand: getBaseCommand(), }, nil }, "secrets disable": func() (cli.Command, error) { return &SecretsDisableCommand{ BaseCommand: getBaseCommand(), }, nil }, "secrets enable": func() (cli.Command, error) { return &SecretsEnableCommand{ BaseCommand: getBaseCommand(), }, nil }, "secrets list": func() (cli.Command, error) { return &SecretsListCommand{ BaseCommand: getBaseCommand(), }, nil }, "secrets move": func() (cli.Command, error) { return &SecretsMoveCommand{ BaseCommand: getBaseCommand(), }, nil }, "secrets tune": func() (cli.Command, error) { return &SecretsTuneCommand{ BaseCommand: getBaseCommand(), }, nil }, "server": func() (cli.Command, error) { return &ServerCommand{ BaseCommand: &BaseCommand{ UI: serverCmdUi, tokenHelper: runOpts.TokenHelper, flagAddress: runOpts.Address, }, AuditBackends: auditBackends, CredentialBackends: credentialBackends, LogicalBackends: logicalBackends, PhysicalBackends: physicalBackends, ServiceRegistrations: serviceRegistrations, ShutdownCh: MakeShutdownCh(), SighupCh: MakeSighupCh(), SigUSR2Ch: MakeSigUSR2Ch(), }, nil }, "ssh": func() (cli.Command, error) { return &SSHCommand{ BaseCommand: getBaseCommand(), }, nil }, "status": func() (cli.Command, error) { return &StatusCommand{ BaseCommand: getBaseCommand(), }, nil }, "token": func() (cli.Command, error) { return &TokenCommand{ BaseCommand: getBaseCommand(), }, nil }, "token create": func() (cli.Command, error) { return &TokenCreateCommand{ BaseCommand: getBaseCommand(), }, nil }, "token capabilities": func() (cli.Command, error) { return &TokenCapabilitiesCommand{ BaseCommand: getBaseCommand(), }, nil }, "token lookup": func() (cli.Command, error) { return &TokenLookupCommand{ BaseCommand: getBaseCommand(), }, nil }, "token renew": func() (cli.Command, error) { return &TokenRenewCommand{ BaseCommand: getBaseCommand(), }, nil }, "token revoke": func() (cli.Command, error) { return &TokenRevokeCommand{ BaseCommand: getBaseCommand(), }, nil }, "unwrap": func() (cli.Command, error) { return &UnwrapCommand{ BaseCommand: getBaseCommand(), }, nil }, "version": func() (cli.Command, error) { return &VersionCommand{ VersionInfo: version.GetVersion(), BaseCommand: getBaseCommand(), }, nil }, "version-history": func() (cli.Command, error) { return &VersionHistoryCommand{ BaseCommand: getBaseCommand(), }, nil }, "write": func() (cli.Command, error) { return &WriteCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv": func() (cli.Command, error) { return &KVCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv put": func() (cli.Command, error) { return &KVPutCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv patch": func() (cli.Command, error) { return &KVPatchCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv rollback": func() (cli.Command, error) { return &KVRollbackCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv get": func() (cli.Command, error) { return &KVGetCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv delete": func() (cli.Command, error) { return &KVDeleteCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv list": func() (cli.Command, error) { return &KVListCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv destroy": func() (cli.Command, error) { return &KVDestroyCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv undelete": func() (cli.Command, error) { return &KVUndeleteCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv enable-versioning": func() (cli.Command, error) { return &KVEnableVersioningCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv metadata": func() (cli.Command, error) { return &KVMetadataCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv metadata put": func() (cli.Command, error) { return &KVMetadataPutCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv metadata patch": func() (cli.Command, error) { return &KVMetadataPatchCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv metadata get": func() (cli.Command, error) { return &KVMetadataGetCommand{ BaseCommand: getBaseCommand(), }, nil }, "kv metadata delete": func() (cli.Command, error) { return &KVMetadataDeleteCommand{ BaseCommand: getBaseCommand(), }, nil }, "monitor": func() (cli.Command, error) { return &MonitorCommand{ BaseCommand: getBaseCommand(), ShutdownCh: MakeShutdownCh(), }, nil }, } // Disabled by default until functional if os.Getenv(OperatorDiagnoseEnableEnv) != "" { Commands["operator diagnose"] = func() (cli.Command, error) { return &OperatorDiagnoseCommand{ BaseCommand: getBaseCommand(), }, nil } } initCommandsEnt(ui, serverCmdUi, runOpts) } // MakeShutdownCh returns a channel that can be used for shutdown // notifications for commands. This channel will send a message for every // SIGINT or SIGTERM received. func MakeShutdownCh() chan struct{} { resultCh := make(chan struct{}) shutdownCh := make(chan os.Signal, 4) signal.Notify(shutdownCh, os.Interrupt, syscall.SIGTERM) go func() { <-shutdownCh close(resultCh) }() return resultCh } // MakeSighupCh returns a channel that can be used for SIGHUP // reloading. This channel will send a message for every // SIGHUP received. func MakeSighupCh() chan struct{} { resultCh := make(chan struct{}) signalCh := make(chan os.Signal, 4) signal.Notify(signalCh, syscall.SIGHUP) go func() { for { <-signalCh resultCh <- struct{}{} } }() return resultCh }