2016-06-23 02:28:45 +08:00
/ *
2018-08-25 03:03:55 +08:00
Copyright The Helm Authors .
2016-06-23 02:28:45 +08:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2025-02-24 23:11:54 +08:00
package cmd
2016-04-21 09:05:12 +08:00
import (
2021-07-26 08:04:58 +08:00
"context"
"fmt"
2016-07-16 07:13:32 +08:00
"io"
2020-06-12 20:24:55 +08:00
"log"
2021-07-26 08:04:58 +08:00
"os"
"os/signal"
"syscall"
2019-05-10 10:53:52 +08:00
"time"
2019-02-09 08:02:57 +08:00
2019-05-23 01:38:11 +08:00
"github.com/pkg/errors"
2016-04-21 09:05:12 +08:00
"github.com/spf13/cobra"
2019-03-13 23:58:35 +08:00
"github.com/spf13/pflag"
2016-04-21 09:05:12 +08:00
2024-12-27 05:33:51 +08:00
"helm.sh/helm/v4/pkg/action"
2025-02-26 04:20:44 +08:00
chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chart/v2/loader"
2024-12-27 05:33:51 +08:00
"helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v4/pkg/cli/values"
2025-02-24 23:11:54 +08:00
"helm.sh/helm/v4/pkg/cmd/require"
2024-12-27 05:33:51 +08:00
"helm.sh/helm/v4/pkg/downloader"
"helm.sh/helm/v4/pkg/getter"
2025-02-26 22:04:32 +08:00
release "helm.sh/helm/v4/pkg/release/v1"
2016-04-21 09:05:12 +08:00
)
const installDesc = `
This command installs a chart archive .
2016-04-29 07:24:08 +08:00
2017-09-26 16:03:00 +08:00
The install argument must be a chart reference , a path to a packaged chart ,
2017-09-26 16:13:54 +08:00
a path to an unpacked chart directory or a URL .
2016-07-23 07:19:06 +08:00
To override values in a chart , use either the ' -- values ' flag and pass in a file
2018-03-15 06:33:02 +08:00
or use the ' -- set ' flag and pass configuration from the command line , to force
2021-12-03 05:03:03 +08:00
a string value use ' -- set - string ' . You can use ' -- set - file ' to set individual
values from a file when the value itself is too long for the command line
2022-02-22 07:48:41 +08:00
or is dynamically generated . You can also use ' -- set - json ' to set json values
2025-02-08 10:04:14 +08:00
( scalars / objects / arrays ) from the command line . Additionally , you can use ' -- set - json ' and passing json object as a string .
2016-07-23 07:19:06 +08:00
2019-10-29 04:28:36 +08:00
$ helm install - f myvalues . yaml myredis . / redis
2016-07-23 07:19:06 +08:00
or
2019-10-29 04:28:36 +08:00
$ helm install -- set name = prod myredis . / redis
2016-07-23 07:19:06 +08:00
2018-03-15 06:33:02 +08:00
or
2019-10-29 04:28:36 +08:00
$ helm install -- set - string long_int = 1234567890 myredis . / redis
2018-03-15 06:33:02 +08:00
2019-09-25 02:42:56 +08:00
or
2020-05-15 21:38:55 +08:00
2019-10-29 04:28:36 +08:00
$ helm install -- set - file my_script = dothings . sh myredis . / redis
2019-09-25 02:42:56 +08:00
2022-02-22 07:48:41 +08:00
or
$ helm install -- set - json ' master . sidecars = [ { "name" : "sidecar" , "image" : "myImage" , "imagePullPolicy" : "Always" , "ports" : [ { "name" : "portname" , "containerPort" : 1234 } ] } ] ' myredis . / redis
2025-02-08 10:04:14 +08:00
or
$ helm install -- set - json ' { "master" : { "sidecars" : [ { "name" : "sidecar" , "image" : "myImage" , "imagePullPolicy" : "Always" , "ports" : [ { "name" : "portname" , "containerPort" : 1234 } ] } ] } } ' myredis . / redis
2022-02-22 07:48:41 +08:00
2016-12-08 06:50:49 +08:00
You can specify the ' -- values '/' - f ' flag multiple times . The priority will be given to the
2016-12-20 14:07:09 +08:00
last ( right - most ) file specified . For example , if both myvalues . yaml and override . yaml
2016-12-08 06:50:49 +08:00
contained a key called ' Test ' , the value set in override . yaml would take precedence :
2019-10-29 04:28:36 +08:00
$ helm install - f myvalues . yaml - f override . yaml myredis . / redis
2016-12-08 06:50:49 +08:00
2017-01-12 07:51:06 +08:00
You can specify the ' -- set ' flag multiple times . The priority will be given to the
last ( right - most ) set specified . For example , if both ' bar ' and ' newbar ' values are
set for a key called ' foo ' , the ' newbar ' value would take precedence :
2019-10-29 04:28:36 +08:00
$ helm install -- set foo = bar -- set foo = newbar myredis . / redis
2017-01-12 07:51:06 +08:00
2022-12-05 13:44:33 +08:00
Similarly , in the following example ' foo ' is set to ' [ "four" ] ' :
2022-02-22 07:48:41 +08:00
$ helm install -- set - json = ' foo = [ "one" , "two" , "three" ] ' -- set - json = ' foo = [ "four" ] ' myredis . / redis
And in the following example , ' foo ' is set to ' { "key1" : "value1" , "key2" : "bar" } ' :
$ helm install -- set - json = ' foo = { "key1" : "value1" , "key2" : "value2" } ' -- set - json = ' foo . key2 = "bar" ' myredis . / redis
2017-01-12 07:51:06 +08:00
2016-07-23 07:19:06 +08:00
To check the generated manifests of a release without installing the chart ,
2024-03-11 02:05:50 +08:00
the -- debug and -- dry - run flags can be combined .
The -- dry - run flag will output all generated chart manifests , including Secrets
2024-03-13 01:56:10 +08:00
which can contain sensitive values . To hide Kubernetes Secrets use the
-- hide - secret flag . Please carefully consider how and when these flags are used .
2016-08-10 06:36:00 +08:00
2017-11-24 02:06:37 +08:00
If -- verify is set , the chart MUST have a provenance file , and the provenance
file MUST pass all verification steps .
2016-10-06 02:43:06 +08:00
2022-08-05 11:02:00 +08:00
There are six different ways you can express the chart you want to install :
2016-10-06 02:43:06 +08:00
2019-10-18 15:52:59 +08:00
1. By chart reference : helm install mymaria example / mariadb
2. By path to a packaged chart : helm install mynginx . / nginx - 1.2 .3 . tgz
3. By path to an unpacked chart directory : helm install mynginx . / nginx
4. By absolute URL : helm install mynginx https : //example.com/charts/nginx-1.2.3.tgz
5. By chart reference and repo url : helm install -- repo https : //example.com/charts/ mynginx nginx
2022-08-05 11:02:00 +08:00
6. By OCI registries : helm install mynginx -- version 1.2 .3 oci : //example.com/charts/nginx
2016-10-06 02:43:06 +08:00
CHART REFERENCES
2019-10-18 15:52:59 +08:00
A chart reference is a convenient way of referencing a chart in a chart repository .
2016-10-06 02:43:06 +08:00
2019-07-27 00:27:18 +08:00
When you use a chart reference with a repo prefix ( ' example / mariadb ' ) , Helm will look in the local
configuration for a chart repository named ' example ' , and will then look for a
2019-10-29 04:28:36 +08:00
chart in that repository whose name is ' mariadb ' . It will install the latest stable version of that chart
until you specify ' -- devel ' flag to also include development version ( alpha , beta , and release candidate releases ) , or
2019-10-24 22:10:47 +08:00
supply a version number with the ' -- version ' flag .
2016-10-06 02:43:06 +08:00
To see the list of chart repositories , use ' helm repo list ' . To search for
charts in a repository , use ' helm search ' .
2016-04-21 09:05:12 +08:00
`
2019-01-08 08:45:14 +08:00
func newInstallCmd ( cfg * action . Configuration , out io . Writer ) * cobra . Command {
2019-02-09 08:02:57 +08:00
client := action . NewInstall ( cfg )
2019-08-02 05:40:52 +08:00
valueOpts := & values . Options { }
2019-10-08 01:17:22 +08:00
var outfmt output . Format
2016-07-16 07:13:32 +08:00
cmd := & cobra . Command {
2018-11-01 06:15:08 +08:00
Use : "install [NAME] [CHART]" ,
Short : "install a chart" ,
2018-04-15 04:31:31 +08:00
Long : installDesc ,
2018-11-01 06:15:08 +08:00
Args : require . MinimumNArgs ( 1 ) ,
2024-03-12 05:13:34 +08:00
ValidArgsFunction : func ( _ * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
2020-04-12 02:06:56 +08:00
return compInstall ( args , toComplete , client )
} ,
2019-02-09 08:02:57 +08:00
RunE : func ( _ * cobra . Command , args [ ] string ) error {
2023-06-07 14:24:02 +08:00
registryClient , err := newRegistryClient ( client . CertFile , client . KeyFile , client . CaFile ,
2024-02-05 21:54:21 +08:00
client . InsecureSkipTLSverify , client . PlainHTTP , client . Username , client . Password )
2022-12-20 05:52:20 +08:00
if err != nil {
return fmt . Errorf ( "missing registry client: %w" , err )
}
client . SetRegistryClient ( registryClient )
2023-07-21 02:26:46 +08:00
// This is for the case where "" is specifically passed in as a
// value. When there is no value passed in NoOptDefVal will be used
// and it is set to client. See addInstallFlags.
2023-05-01 13:04:04 +08:00
if client . DryRunOption == "" {
2023-04-09 07:43:01 +08:00
client . DryRunOption = "none"
}
2019-08-01 23:04:36 +08:00
rel , err := runInstall ( args , client , valueOpts , out )
2016-07-16 07:13:32 +08:00
if err != nil {
2021-08-27 08:50:24 +08:00
return errors . Wrap ( err , "INSTALLATION FAILED" )
2016-07-16 07:13:32 +08:00
}
2019-09-26 02:20:47 +08:00
2024-11-16 02:27:28 +08:00
return outfmt . Write ( out , & statusPrinter {
release : rel ,
debug : settings . Debug ,
showMetadata : false ,
hideNotes : client . HideNotes ,
} )
2016-07-16 07:13:32 +08:00
} ,
2016-05-20 06:49:45 +08:00
}
2016-07-16 07:13:32 +08:00
2020-06-12 20:24:55 +08:00
addInstallFlags ( cmd , cmd . Flags ( ) , client , valueOpts )
2024-03-13 01:56:10 +08:00
// hide-secret is not available in all places the install flags are used so
// it is added separately
f := cmd . Flags ( )
f . BoolVar ( & client . HideSecret , "hide-secret" , false , "hide Kubernetes Secrets when also using the --dry-run flag" )
2019-10-08 01:17:22 +08:00
bindOutputFlag ( cmd , & outfmt )
2019-09-24 01:29:24 +08:00
bindPostRenderFlag ( cmd , & client . PostRenderer )
2016-10-15 11:05:04 +08:00
2016-07-16 07:13:32 +08:00
return cmd
}
2020-06-12 20:24:55 +08:00
func addInstallFlags ( cmd * cobra . Command , f * pflag . FlagSet , client * action . Install , valueOpts * values . Options ) {
2020-02-21 03:56:03 +08:00
f . BoolVar ( & client . CreateNamespace , "create-namespace" , false , "create the release namespace if not present" )
2023-07-21 02:26:46 +08:00
// --dry-run options with expected outcome:
// - Not set means no dry run and server is contacted.
// - Set with no value, a value of client, or a value of true and the server is not contacted
// - Set with a value of false, none, or false and the server is contacted
// The true/false part is meant to reflect some legacy behavior while none is equal to "".
2023-05-01 13:04:04 +08:00
f . StringVar ( & client . DryRunOption , "dry-run" , "" , "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections." )
2023-01-24 04:28:29 +08:00
f . Lookup ( "dry-run" ) . NoOptDefVal = "client"
2022-12-24 02:28:01 +08:00
f . BoolVar ( & client . Force , "force" , false , "force resource updates through a replacement strategy" )
2019-03-13 23:58:35 +08:00
f . BoolVar ( & client . DisableHooks , "no-hooks" , false , "prevent hooks from running during install" )
2024-11-07 19:30:34 +08:00
f . BoolVar ( & client . Replace , "replace" , false , "reuse the given name, only if that name is a deleted release which remains in the history. This is unsafe in production" )
2025-02-18 21:50:04 +08:00
f . DurationVar ( & client . Timeout , "timeout" , 300 * time . Second , "time to wait for any individual Kubernetes operation (like Jobs for hooks)" )
2020-11-03 19:48:29 +08:00
f . BoolVar ( & client . WaitForJobs , "wait-for-jobs" , false , "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout" )
2019-03-13 23:58:35 +08:00
f . BoolVarP ( & client . GenerateName , "generate-name" , "g" , false , "generate the name (and omit the NAME parameter)" )
f . StringVar ( & client . NameTemplate , "name-template" , "" , "specify template used to name the release" )
2019-12-10 00:48:31 +08:00
f . StringVar ( & client . Description , "description" , "" , "add a custom description" )
2019-09-30 23:40:33 +08:00
f . BoolVar ( & client . Devel , "devel" , false , "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored" )
2021-04-29 04:21:02 +08:00
f . BoolVar ( & client . DependencyUpdate , "dependency-update" , false , "update dependencies if they are missing before installing the chart" )
2019-10-30 02:17:54 +08:00
f . BoolVar ( & client . DisableOpenAPIValidation , "disable-openapi-validation" , false , "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema" )
2025-02-17 05:10:06 +08:00
f . BoolVar ( & client . Atomic , "atomic" , false , "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically to \"watcher\" if --atomic is used" )
2019-09-30 23:40:33 +08:00
f . BoolVar ( & client . SkipCRDs , "skip-crds" , false , "if set, no CRDs will be installed. By default, CRDs are installed if not already present" )
f . BoolVar ( & client . SubNotes , "render-subchart-notes" , false , "if set, render subchart notes along with the parent" )
2024-01-23 23:32:03 +08:00
f . BoolVar ( & client . SkipSchemaValidation , "skip-schema-validation" , false , "if set, disables JSON schema validation" )
2023-02-17 07:56:59 +08:00
f . StringToStringVarP ( & client . Labels , "labels" , "l" , nil , "Labels that would be added to release metadata. Should be divided by comma." )
2023-02-02 00:35:19 +08:00
f . BoolVar ( & client . EnableDNS , "enable-dns" , false , "enable DNS lookups when rendering templates" )
2022-12-05 13:44:33 +08:00
f . BoolVar ( & client . HideNotes , "hide-notes" , false , "if set, do not show notes in install output. Does not affect presence in chart metadata" )
2024-11-19 12:33:03 +08:00
f . BoolVar ( & client . TakeOwnership , "take-ownership" , false , "if set, install will ignore the check for helm annotations and take ownership of the existing resources" )
2019-08-01 23:04:36 +08:00
addValueOptionsFlags ( f , valueOpts )
2019-03-13 23:58:35 +08:00
addChartPathOptionsFlags ( f , & client . ChartPathOptions )
2025-03-25 21:55:39 +08:00
AddWaitFlag ( cmd , & client . WaitStrategy )
2020-06-12 20:24:55 +08:00
2024-03-12 05:13:34 +08:00
err := cmd . RegisterFlagCompletionFunc ( "version" , func ( _ * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
2020-06-12 20:24:55 +08:00
requiredArgs := 2
if client . GenerateName {
requiredArgs = 1
}
if len ( args ) != requiredArgs {
return nil , cobra . ShellCompDirectiveNoFileComp
}
return compVersionFlag ( args [ requiredArgs - 1 ] , toComplete )
} )
if err != nil {
log . Fatal ( err )
}
2019-03-13 23:58:35 +08:00
}
2019-08-02 05:40:52 +08:00
func runInstall ( args [ ] string , client * action . Install , valueOpts * values . Options , out io . Writer ) ( * release . Release , error ) {
2025-02-24 23:11:54 +08:00
Debug ( "Original chart version: %q" , client . Version )
2019-02-09 08:02:57 +08:00
if client . Version == "" && client . Devel {
2025-02-24 23:11:54 +08:00
Debug ( "setting version to >0.0.0-0" )
2019-02-09 08:02:57 +08:00
client . Version = ">0.0.0-0"
2018-11-01 06:15:08 +08:00
}
2019-02-09 08:02:57 +08:00
name , chart , err := client . NameAndChart ( args )
if err != nil {
return nil , err
2018-11-01 06:15:08 +08:00
}
2019-02-09 08:02:57 +08:00
client . ReleaseName = name
2018-11-01 06:15:08 +08:00
2019-02-09 08:02:57 +08:00
cp , err := client . ChartPathOptions . LocateChart ( chart , settings )
2016-05-19 03:58:10 +08:00
if err != nil {
2019-02-09 08:02:57 +08:00
return nil , err
2016-05-19 03:58:10 +08:00
}
2025-02-24 23:11:54 +08:00
Debug ( "CHART PATH: %s\n" , cp )
2019-02-09 08:02:57 +08:00
2019-08-23 14:31:50 +08:00
p := getter . All ( settings )
vals , err := valueOpts . MergeValues ( p )
2019-08-01 23:04:36 +08:00
if err != nil {
2019-02-09 08:02:57 +08:00
return nil , err
2016-08-02 10:19:27 +08:00
}
2018-11-29 02:20:33 +08:00
// Check chart dependencies to make sure all are present in /charts
2019-02-09 08:02:57 +08:00
chartRequested , err := loader . Load ( cp )
2017-04-08 05:42:15 +08:00
if err != nil {
2019-02-09 08:02:57 +08:00
return nil , err
2017-04-08 05:42:15 +08:00
}
2019-11-19 15:41:15 +08:00
if err := checkIfInstallable ( chartRequested ) ; err != nil {
2019-02-09 08:02:57 +08:00
return nil , err
2019-02-12 23:25:28 +08:00
}
2019-12-14 00:50:25 +08:00
if chartRequested . Metadata . Deprecated {
2025-03-31 15:51:37 +08:00
logger . Warn ( "this chart is deprecated" )
2019-12-14 00:50:25 +08:00
}
2018-11-29 02:20:33 +08:00
if req := chartRequested . Metadata . Dependencies ; req != nil {
2019-02-09 08:02:57 +08:00
// If CheckDependencies returns an error, we have unfulfilled dependencies.
2017-04-28 06:34:18 +08:00
// As of Helm 2.4.0, this is treated as a stopping condition:
2018-11-29 02:08:38 +08:00
// https://github.com/helm/helm/issues/2209
2019-02-09 08:02:57 +08:00
if err := action . CheckDependencies ( chartRequested , req ) ; err != nil {
2021-06-11 17:56:07 +08:00
err = errors . Wrap ( err , "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies" )
2019-02-09 08:02:57 +08:00
if client . DependencyUpdate {
2017-08-31 14:19:59 +08:00
man := & downloader . Manager {
2019-08-23 14:31:50 +08:00
Out : out ,
ChartPath : cp ,
Keyring : client . ChartPathOptions . Keyring ,
SkipUpdate : false ,
Getters : p ,
RepositoryConfig : settings . RepositoryConfig ,
RepositoryCache : settings . RepositoryCache ,
2020-04-16 06:17:57 +08:00
Debug : settings . Debug ,
2023-03-28 14:52:16 +08:00
RegistryClient : client . GetRegistryClient ( ) ,
2017-08-31 14:19:59 +08:00
}
if err := man . Update ( ) ; err != nil {
2019-02-09 08:02:57 +08:00
return nil , err
2017-08-31 14:19:59 +08:00
}
2020-04-16 06:17:57 +08:00
// Reload the chart with the updated Chart.lock file.
if chartRequested , err = loader . Load ( cp ) ; err != nil {
return nil , errors . Wrap ( err , "failed reloading chart after repo update" )
}
2017-08-31 14:19:59 +08:00
} else {
2019-02-09 08:02:57 +08:00
return nil , err
2017-02-11 03:16:44 +08:00
}
}
}
2017-04-28 06:34:18 +08:00
2019-10-11 02:35:46 +08:00
client . Namespace = settings . Namespace ( )
2021-07-26 08:04:58 +08:00
2023-01-31 07:04:10 +08:00
// Validate DryRunOption member is one of the allowed values
2023-01-24 03:18:59 +08:00
if err := validateDryRunOptionFlag ( client . DryRunOption ) ; err != nil {
2023-01-16 16:29:19 +08:00
return nil , err
}
2021-07-26 08:04:58 +08:00
// Create context and prepare the handle of SIGTERM
ctx := context . Background ( )
ctx , cancel := context . WithCancel ( ctx )
2021-11-16 19:21:36 +08:00
// Set up channel on which to send signal notifications.
// We must use a buffered channel or risk missing the signal
// if we're not ready to receive when the signal is sent.
cSignal := make ( chan os . Signal , 2 )
2021-07-26 08:04:58 +08:00
signal . Notify ( cSignal , os . Interrupt , syscall . SIGTERM )
go func ( ) {
<- cSignal
2021-08-07 00:54:00 +08:00
fmt . Fprintf ( out , "Release %s has been cancelled.\n" , args [ 0 ] )
2021-07-26 08:04:58 +08:00
cancel ( )
} ( )
return client . RunWithContext ( ctx , chartRequested , vals )
2017-02-11 03:16:44 +08:00
}
2019-05-23 01:38:11 +08:00
2019-11-19 15:41:15 +08:00
// checkIfInstallable validates if a chart can be installed
2019-05-23 01:38:11 +08:00
//
// Application chart type is only installable
2019-11-19 15:41:15 +08:00
func checkIfInstallable ( ch * chart . Chart ) error {
2019-05-23 01:38:11 +08:00
switch ch . Metadata . Type {
case "" , "application" :
2019-11-19 15:41:15 +08:00
return nil
2019-05-23 01:38:11 +08:00
}
2019-11-19 15:41:15 +08:00
return errors . Errorf ( "%s charts are not installable" , ch . Metadata . Type )
2019-05-23 01:38:11 +08:00
}
2019-12-31 21:42:12 +08:00
// Provide dynamic auto-completion for the install and template commands
2020-04-12 02:06:56 +08:00
func compInstall ( args [ ] string , toComplete string , client * action . Install ) ( [ ] string , cobra . ShellCompDirective ) {
2019-12-31 21:59:10 +08:00
requiredArgs := 1
if client . GenerateName {
requiredArgs = 0
}
if len ( args ) == requiredArgs {
2019-12-31 21:42:12 +08:00
return compListCharts ( toComplete , true )
}
2020-04-12 02:06:56 +08:00
return nil , cobra . ShellCompDirectiveNoFileComp
2019-12-31 21:42:12 +08:00
}
2023-01-16 16:29:19 +08:00
2023-01-24 03:18:59 +08:00
func validateDryRunOptionFlag ( dryRunOptionFlagValue string ) error {
2023-01-31 07:04:10 +08:00
// Validate dry-run flag value with a set of allowed value
2023-01-16 16:29:19 +08:00
allowedDryRunValues := [ ] string { "false" , "true" , "none" , "client" , "server" }
isAllowed := false
for _ , v := range allowedDryRunValues {
2023-01-24 03:18:59 +08:00
if dryRunOptionFlagValue == v {
2023-01-16 16:29:19 +08:00
isAllowed = true
break
}
}
if ! isAllowed {
2023-01-31 07:04:10 +08:00
return errors . New ( "Invalid dry-run flag. Flag must one of the following: false, true, none, client, server" )
2023-01-17 02:34:01 +08:00
}
2023-01-16 16:29:19 +08:00
return nil
}