You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
portainer/api/cli/cli.go

149 lines
7.1 KiB

package cli
import (
"errors"
"os"
"path/filepath"
"strings"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/rs/zerolog/log"
"gopkg.in/alecthomas/kingpin.v2"
)
// Service implements the CLIService interface
type Service struct{}
var (
errInvalidEndpointProtocol = errors.New("Invalid environment protocol: Portainer only supports unix://, npipe:// or tcp://")
errSocketOrNamedPipeNotFound = errors.New("Unable to locate Unix socket or named pipe")
errInvalidSnapshotInterval = errors.New("Invalid snapshot interval")
errAdminPassExcludeAdminPassFile = errors.New("Cannot use --admin-password with --admin-password-file")
)
// ParseFlags parse the CLI flags and return a portainer.Flags struct
func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
kingpin.Version(version)
flags := &portainer.CLIFlags{
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
AddrHTTPS: kingpin.Flag("bind-https", "Address and port to serve Portainer via https").Default(defaultHTTPSBindAddress).String(),
TunnelAddr: kingpin.Flag("tunnel-addr", "Address to serve the tunnel server").Default(defaultTunnelServerAddress).String(),
TunnelPort: kingpin.Flag("tunnel-port", "Port to serve the tunnel server").Default(defaultTunnelServerPort).String(),
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
DemoEnvironment: kingpin.Flag("demo", "Demo environment").Bool(),
EndpointURL: kingpin.Flag("host", "Environment URL").Short('H').String(),
FeatureFlags: kingpin.Flag("feat", "List of feature flags").Strings(),
EnableEdgeComputeFeatures: kingpin.Flag("edge-compute", "Enable Edge Compute features").Bool(),
NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app (deprecated)").Bool(),
TLS: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLS).Bool(),
TLSSkipVerify: kingpin.Flag("tlsskipverify", "Disable TLS server verification").Default(defaultTLSSkipVerify).Bool(),
TLSCacert: kingpin.Flag("tlscacert", "Path to the CA").Default(defaultTLSCACertPath).String(),
TLSCert: kingpin.Flag("tlscert", "Path to the TLS certificate file").Default(defaultTLSCertPath).String(),
TLSKey: kingpin.Flag("tlskey", "Path to the TLS key").Default(defaultTLSKeyPath).String(),
HTTPDisabled: kingpin.Flag("http-disabled", "Serve portainer only on https").Default(defaultHTTPDisabled).Bool(),
HTTPEnabled: kingpin.Flag("http-enabled", "Serve portainer on http").Default(defaultHTTPEnabled).Bool(),
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL (deprecated)").Default(defaultSSL).Bool(),
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").String(),
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").String(),
Rollback: kingpin.Flag("rollback", "Rollback the database to the previous backup").Bool(),
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each environment snapshot job").String(),
feat(docker-desktop-extension): Make Portainer compatible with Docker Desktop Extension EE-2747 (#6644) * Initial extension build * Add auto login fix auto auth add some message Add extension version Double attempt to login Add auto login from jwt check Add autologin on logout revert sidebar Catch error 401 to relogin cleanup login Add password generator Hide User block and collapse sidebar by default hide user box and toggle sidebar remove defailt dd Integrate extension to portainer Move extension to build remove files from ignore Move extension folder fix alpine try to copy folder try add Change base image move folder extension ignore folder build Fix relative path Move ext to root fix image name versioned index Update extension on same image Update mod * fix kubeshell baseurl * Fix kube shell * move build and remove https * Tidy mod * Remove space * Fix hash test * Password manager * change to building locally * Restore version variable and add local install command * fix local dev image + hide users & auth * Password manageListen on locahost onlyr * FIxes base path * Hide only username * Move default to constants * Update app/portainer/components/PageHeader/HeaderContent.html Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com> * fix 2 failing FE tests [EE-2938] * remove password autogeneration from v1 * fix webhooks * fix docker container console and attach * fix default for portainer IP * update meta, dockerfile and makefile for new ver * fix basepath in kube and docker console * revert makefile changes * add icon back * Add remote short cut command * make local methods the default * default to 0.0.0 for version for local development * simplify make commands * small build fixes * resolve conflicts * Update api/filesystem/write.go Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com> * use a more secure default pass Co-authored-by: itsconquest <william.conquest@portainer.io> Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
3 years ago
AdminPassword: kingpin.Flag("admin-password", "Set admin password with provided hash").String(),
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),
Labels: pairs(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')),
Logo: kingpin.Flag("logo", "URL for the logo displayed in the UI").String(),
Templates: kingpin.Flag("templates", "URL to the templates definitions.").Short('t').String(),
BaseURL: kingpin.Flag("base-url", "Base URL parameter such as portainer if running portainer as http://yourdomain.com/portainer/.").Short('b').Default(defaultBaseURL).String(),
InitialMmapSize: kingpin.Flag("initial-mmap-size", "Initial mmap size of the database in bytes").Int(),
MaxBatchSize: kingpin.Flag("max-batch-size", "Maximum size of a batch").Int(),
MaxBatchDelay: kingpin.Flag("max-batch-delay", "Maximum delay before a batch starts").Duration(),
SecretKeyName: kingpin.Flag("secret-key-name", "Secret key name for encryption and will be used as /run/secrets/<secret-key-name>.").Default(defaultSecretKeyName).String(),
LogLevel: kingpin.Flag("log-level", "Set the minimum logging level to show").Default("INFO").Enum("DEBUG", "INFO", "WARN", "ERROR"),
LogMode: kingpin.Flag("log-mode", "Set the logging output mode").Default("PRETTY").Enum("NOCOLOR", "PRETTY", "JSON"),
}
kingpin.Parse()
if !filepath.IsAbs(*flags.Assets) {
ex, err := os.Executable()
if err != nil {
panic(err)
}
*flags.Assets = filepath.Join(filepath.Dir(ex), *flags.Assets)
}
return flags, nil
}
// ValidateFlags validates the values of the flags.
func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
displayDeprecationWarnings(flags)
err := validateEndpointURL(*flags.EndpointURL)
if err != nil {
return err
}
err = validateSnapshotInterval(*flags.SnapshotInterval)
if err != nil {
return err
}
if *flags.AdminPassword != "" && *flags.AdminPasswordFile != "" {
return errAdminPassExcludeAdminPassFile
}
return nil
}
func displayDeprecationWarnings(flags *portainer.CLIFlags) {
if *flags.NoAnalytics {
log.Warn().Msg("the --no-analytics flag has been kept to allow migration of instances running a previous version of Portainer with this flag enabled, to version 2.0 where enabling this flag will have no effect")
}
if *flags.SSL {
log.Warn().Msg("SSL is enabled by default and there is no need for the --ssl flag, it has been kept to allow migration of instances running a previous version of Portainer with this flag enabled")
}
}
func validateEndpointURL(endpointURL string) error {
if endpointURL == "" {
return nil
}
if !strings.HasPrefix(endpointURL, "unix://") && !strings.HasPrefix(endpointURL, "tcp://") && !strings.HasPrefix(endpointURL, "npipe://") {
return errInvalidEndpointProtocol
}
if strings.HasPrefix(endpointURL, "unix://") || strings.HasPrefix(endpointURL, "npipe://") {
socketPath := strings.TrimPrefix(endpointURL, "unix://")
socketPath = strings.TrimPrefix(socketPath, "npipe://")
if _, err := os.Stat(socketPath); err != nil {
if os.IsNotExist(err) {
return errSocketOrNamedPipeNotFound
}
return err
}
}
return nil
}
func validateSnapshotInterval(snapshotInterval string) error {
if snapshotInterval == "" {
return nil
}
_, err := time.ParseDuration(snapshotInterval)
if err != nil {
return errInvalidSnapshotInterval
}
return nil
}