// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package cli

import (
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/aws/copilot-cli/internal/pkg/manifest"
	"github.com/aws/copilot-cli/internal/pkg/manifest/manifestinfo"
	"github.com/dustin/go-humanize/english"
)

// Long flag names.
const (
	// Common flags.
	nameFlag           = "name"
	appFlag            = "app"
	envFlag            = "env"
	workloadFlag       = "workload"
	svcTypeFlag        = "svc-type"
	jobTypeFlag        = "job-type"
	typeFlag           = "type"
	profileFlag        = "profile"
	yesFlag            = "yes"
	jsonFlag           = "json"
	allFlag            = "all"
	forceFlag          = "force"
	allowDowngradeFlag = "allow-downgrade"
	noRollbackFlag     = "no-rollback"
	manifestFlag       = "manifest"
	resourceTagsFlag   = "resource-tags"
	detachFlag         = "detach"

	// Deploy flags.
	yesInitWorkloadFlag = "init-wkld"

	// Build flags.
	dockerFileFlag          = "dockerfile"
	dockerFileContextFlag   = "build-context"
	dockerFileBuildArgsFlag = "build-args"
	imageTagFlag            = "tag"
	stackOutputDirFlag      = "output-dir"
	uploadAssetsFlag        = "upload-assets"
	deployFlag              = "deploy"
	diffFlag                = "diff"
	diffAutoApproveFlag     = "diff-yes"
	sourcesFlag             = "sources"

	// Flags for operational commands.
	limitFlag                   = "limit"
	lastFlag                    = "last"
	followFlag                  = "follow"
	previousFlag                = "previous"
	sinceFlag                   = "since"
	startTimeFlag               = "start-time"
	endTimeFlag                 = "end-time"
	tasksFlag                   = "tasks"
	logGroupFlag                = "log-group"
	containerLogFlag            = "container"
	includeStateMachineLogsFlag = "include-state-machine"
	resourcesFlag               = "resources"
	taskIDFlag                  = "task-id"
	containerFlag               = "container"

	// Run local flags
	portOverrideFlag   = "port-override"
	envVarOverrideFlag = "env-var-override"
	proxyFlag          = "proxy"
	proxyNetworkFlag   = "proxy-network"
	watchFlag          = "watch"
	useTaskRoleFlag    = "use-task-role"

	// Flags for CI/CD.
	githubURLFlag         = "github-url"
	repoURLFlag           = "url"
	githubAccessTokenFlag = "github-access-token"
	gitBranchFlag         = "git-branch"
	envsFlag              = "environments"
	pipelineTypeFlag      = "pipeline-type"

	// Flags for ls.
	localFlag = "local"

	// Flags for storage.
	storageTypeFlag                    = "storage-type"
	storageLifecycleFlag               = "lifecycle"
	storageAddIngressFromFlag          = "add-ingress-from"
	storagePartitionKeyFlag            = "partition-key"
	storageSortKeyFlag                 = "sort-key"
	storageNoSortFlag                  = "no-sort"
	storageLSIConfigFlag               = "lsi"
	storageNoLSIFlag                   = "no-lsi"
	storageAuroraServerlessVersionFlag = "serverless-version"
	storageRDSEngineFlag               = "engine"
	storageRDSInitialDBFlag            = "initial-db"
	storageRDSParameterGroupFlag       = "parameter-group"

	// Flags for one-off tasks.
	taskGroupNameFlag            = "task-group-name"
	countFlag                    = "count"
	cpuFlag                      = "cpu"
	memoryFlag                   = "memory"
	imageFlag                    = "image"
	taskRoleFlag                 = "task-role"
	executionRoleFlag            = "execution-role"
	clusterFlag                  = "cluster"
	acknowledgeSecretsAccessFlag = "acknowledge-secrets-access"
	subnetsFlag                  = "subnets"
	securityGroupsFlag           = "security-groups"
	envVarsFlag                  = "env-vars"
	envFileFlag                  = "env-file"
	secretsFlag                  = "secrets"
	commandFlag                  = "command"
	entrypointFlag               = "entrypoint"
	taskDefaultFlag              = "default"
	generateCommandFlag          = "generate-cmd"
	osFlag                       = "platform-os"
	archFlag                     = "platform-arch"

	// Flags for environment configurations.
	vpcIDFlag                      = "import-vpc-id"
	publicSubnetsFlag              = "import-public-subnets"
	privateSubnetsFlag             = "import-private-subnets"
	certsFlag                      = "import-cert-arns"
	internalALBSubnetsFlag         = "internal-alb-subnets"
	allowVPCIngressFlag            = "internal-alb-allow-vpc-ingress"
	overrideVPCCIDRFlag            = "override-vpc-cidr"
	overrideAZsFlag                = "override-az-names"
	overridePublicSubnetCIDRsFlag  = "override-public-cidrs"
	overridePrivateSubnetCIDRsFlag = "override-private-cidrs"

	enableContainerInsightsFlag = "container-insights"
	defaultConfigFlag           = "default-config"

	accessKeyIDFlag     = "aws-access-key-id"
	secretAccessKeyFlag = "aws-secret-access-key"
	sessionTokenFlag    = "aws-session-token"
	regionFlag          = "region"

	// Flags for creating secrets.
	valuesFlag        = "values"
	overwriteFlag     = "overwrite"
	inputFilePathFlag = "cli-input-yaml"

	// Flags for overriding templates.
	iacToolFlag       = "tool"
	cdkLanguageFlag   = "cdk-language"
	skipResourcesFlag = "skip-resources"

	// Other.
	svcPortFlag             = "port"
	noSubscriptionFlag      = "no-subscribe"
	subscribeTopicsFlag     = "subscribe-topics"
	ingressTypeFlag         = "ingress-type"
	retriesFlag             = "retries"
	timeoutFlag             = "timeout"
	scheduleFlag            = "schedule"
	domainNameFlag          = "domain"
	permissionsBoundaryFlag = "permissions-boundary"
	prodEnvFlag             = "prod"
	deleteSecretFlag        = "delete-secret"
	deployEnvFlag           = "deploy-env"
	yesInitEnvFlag          = "init-env"
)

// Short flag names.
// A short flag only exists if the flag or flag set is mandatory by the command.
const (
	nameFlagShort     = "n"
	appFlagShort      = "a"
	envFlagShort      = "e"
	typeFlagShort     = "t"
	workloadFlagShort = "w"
	previousFlagShort = "p"

	dockerFileFlagShort        = "d"
	commandFlagShort           = "c"
	imageFlagShort             = "i"
	repoURLFlagShort           = "u"
	githubAccessTokenFlagShort = "t"
	gitBranchFlagShort         = "b"
	envsFlagShort              = "e"
	pipelineTypeShort          = "p"
	storageLifecycleShort      = "l"

	scheduleFlagShort = "s"
)

// Descriptions for flags.
var (
	svcTypeFlagDescription = fmt.Sprintf(`Type of service to create. Must be one of:
%s.`, strings.Join(applyAll(manifestinfo.ServiceTypes(), strconv.Quote), ", "))
	imageFlagDescription = fmt.Sprintf(`The location of an existing Docker image.
Cannot be specified with --%s or --%s.`, dockerFileFlag, dockerFileContextFlag)
	dockerFileFlagDescription = fmt.Sprintf(`Path to the Dockerfile.
Cannot be specified with --%s.`, imageFlag)
	dockerFileBuildArgsFlagDescription = fmt.Sprintf(`Key-value pairs converted to --build-args.
Cannot be specified with --%s.`, imageFlag)
	dockerFileContextFlagDescription = fmt.Sprintf(`Path to the Docker build context.
Cannot be specified with --%s.`, imageFlag)
	sourcesFlagDescription = fmt.Sprintf(`List of relative paths to source directories or files.
Must be specified with '--%s "Static Site"'.`, svcTypeFlag)
	storageTypeFlagDescription = fmt.Sprintf(`Type of storage to add. Must be one of:
%s.`, strings.Join(applyAll(storageTypes, strconv.Quote), ", "))
	storageLifecycleFlagDescription = fmt.Sprintf(`Whether the storage should be created and deleted
at the same time as a workload or an environment.
Must be one of: %s.`, english.OxfordWordSeries(applyAll(validLifecycleOptions, strconv.Quote), "or"))
	storageAddIngressFromFlagDescription = fmt.Sprintf(`The workload that needs access to an
environment storage resource. Must be
specified with %q and %q.
Can be specified with %q.`,
		fmt.Sprintf("--%s", nameFlag),
		fmt.Sprintf("--%s", storageTypeFlag),
		fmt.Sprintf("--%s", storageRDSEngineFlag))
	jobTypeFlagDescription = fmt.Sprintf(`Type of job to create. Must be one of:
%s.`, strings.Join(applyAll(manifestinfo.JobTypes(), strconv.Quote), ", "))
	wkldTypeFlagDescription = fmt.Sprintf(`Type of job or svc to create. Must be one of:
%s.`, strings.Join(applyAll(manifestinfo.WorkloadTypes(), strconv.Quote), ", "))

	clusterFlagDescription = fmt.Sprintf(`Optional. The short name or full ARN of the cluster to run the task in. 
Cannot be specified with --%s, --%s or --%s.`, appFlag, envFlag, taskDefaultFlag)
	acknowledgeSecretsAccessDescription = fmt.Sprintf(`Optional. Skip the confirmation question and grant access to the secrets specified by --secrets flag. 
This flag is useful only when '%s' flag is specified`, secretsFlag)
	subnetsFlagDescription = fmt.Sprintf(`Optional. The subnet IDs for the task to use. Can be specified multiple times.
Cannot be specified with --%s, --%s or --%s.`, appFlag, envFlag, taskDefaultFlag)
	securityGroupsFlagDescription = "Optional. Additional security group IDs for the task to use. Can be specified multiple times."
	taskRunDefaultFlagDescription = fmt.Sprintf(`Optional. Run tasks in default cluster and default subnets. 
Cannot be specified with --%s, --%s or --%s.`, appFlag, envFlag, subnetsFlag)
	taskExecDefaultFlagDescription = fmt.Sprintf(`Optional. Execute commands in running tasks in default cluster and default subnets. 
Cannot be specified with --%s or --%s.`, appFlag, envFlag)
	taskDeleteDefaultFlagDescription = fmt.Sprintf(`Optional. Delete a task which was launched in the default cluster and subnets.
Cannot be specified with --%s or --%s.`, appFlag, envFlag)
	taskEnvFlagDescription = fmt.Sprintf(`Optional. Name of the environment.
Cannot be specified with --%s, --%s or --%s.`, taskDefaultFlag, subnetsFlag, securityGroupsFlag)
	taskAppFlagDescription = fmt.Sprintf(`Optional. Name of the application.
Cannot be specified with --%s, --%s or --%s.`, taskDefaultFlag, subnetsFlag, securityGroupsFlag)
	osFlagDescription   = fmt.Sprintf(`Optional. Operating system of the task. Must be specified along with '%s'.`, archFlag)
	archFlagDescription = fmt.Sprintf(`Optional. Architecture of the task. Must be specified along with '%s'.`, osFlag)

	secretNameFlagDescription = fmt.Sprintf(`The name of the secret.
Mutually exclusive with the --%s flag.`, inputFilePathFlag)
	secretValuesFlagDescription = fmt.Sprintf(`Values of the secret in each environment. Specified as <environment>=<value> separated by commas.
Mutually exclusive with the --%s flag.`, inputFilePathFlag)
	secretInputFilePathFlagDescription = fmt.Sprintf(`Optional. A YAML file in which the secret values are specified.
Mutually exclusive with the -%s ,--%s and --%s flags.`, nameFlagShort, nameFlag, valuesFlag)

	iacToolFlagDescription = fmt.Sprintf(`Infrastructure as Code tool to override a template.
Must be one of: %s.`, strings.Join(applyAll(validIaCTools, strconv.Quote), ", "))
	cdkLanguageFlagDescription = `Optional. The Cloud Development Kit language.`
	overrideEnvFlagDescription = `Optional. Name of the environment to use when retrieving resources in a template.
Defaults to a random environment.`
	skipResourcesFlagDescription = `Optional. Skip asking for which resources to override and generate empty IaC extension files.`

	repoURLFlagDescription = fmt.Sprintf(`The repository URL to trigger your pipeline.
Supported providers are: %s.`, strings.Join(manifest.PipelineProviders, ", "))

	ingressTypeFlagDescription = fmt.Sprintf(`Required for a Request-Driven Web Service. Allowed source of traffic to your service.
Must be one of %s.`, english.OxfordWordSeries(rdwsIngressOptions, "or"))
)

const (
	// Common flags
	appFlagDescription          = "Name of the application."
	envFlagDescription          = "Name of the environment."
	svcFlagDescription          = "Name of the service."
	jobFlagDescription          = "Name of the job."
	workloadFlagDescription     = "Name of the service or job."
	workloadsFlagDescription    = "Names of the service or jobs to deploy, with an optional priority tag (e.g. fe/1, be/2, my-job/1)."
	nameFlagDescription         = "Name of the service, job, or task group."
	yesFlagDescription          = "Skips confirmation prompt."
	resourceTagsFlagDescription = `Optional. Labels with a key and value separated by commas.
Allows you to categorize resources.`
	diffFlagDescription            = "Compares the generated CloudFormation template to the deployed stack."
	diffAutoApproveFlagDescription = "Skip interactive approval of diff before deploying."

	// Deployment.
	deployFlagDescription         = `Deploy your service or job to a new or existing environment.`
	allowDowngradeFlagDescription = `Optional. Allow using an older version of Copilot to update Copilot components
updated by a newer version of Copilot.`
	forceFlagDescription = `Optional. Force a new service deployment using the existing image.
Not available with the "Static Site" service type.`
	noRollbackFlagDescription = `Optional. Disable automatic stack 
rollback in case of deployment failure.
We do not recommend using this flag for a
production environment.`
	forceEnvDeployFlagDescription  = "Optional. Force update the environment stack template."
	yesInitWorkloadFlagDescription = "Optional. When specified with --all, initialize all local workloads before deployment."
	allWorkloadsFlagDescription    = "Optional. Deploy all workloads with manifests in the current Copilot workspace."
	detachFlagDescription          = "Optional. Skip displaying CloudFormation deployment progress."

	// Operational.
	jsonFlagDescription = "Optional. Output in JSON format."

	limitFlagDescription = `Optional. The maximum number of log events returned. Default is 10
unless any time filtering flags are set.`
	lastFlagDescription = `Optional. The number of executions of the scheduled job for which
logs should be shown.`
	followFlagDescription   = "Optional. Specifies if the logs should be streamed."
	previousFlagDescription = "Optional. Print logs for the last stopped task if exists."
	sinceFlagDescription    = `Optional. Only return logs newer than a relative duration like 5s, 2m, or 3h.
Defaults to all logs. Only one of start-time / since may be used.`
	startTimeFlagDescription = `Optional. Only return logs after a specific date (RFC3339).
Defaults to all logs. Only one of start-time / since may be used.`
	endTimeFlagDescription = `Optional. Only return logs before a specific date (RFC3339).
Defaults to all logs. Only one of end-time / follow may be used.`
	tasksLogsFlagDescription               = "Optional. Only return logs from specific task IDs."
	includeStateMachineLogsFlagDescription = "Optional. Include logs from the state machine executions."
	logGroupFlagDescription                = "Optional. Only return logs from specific log group."
	containerLogFlagDescription            = "Optional. Return only logs from a specific container."

	envResourcesFlagDescription      = "Optional. Show the resources in your environment."
	svcResourcesFlagDescription      = "Optional. Show the resources in your service."
	pipelineResourcesFlagDescription = "Optional. Show the resources in your pipeline."
	localSvcFlagDescription          = "Only show services in the workspace."
	localJobFlagDescription          = "Only show jobs in the workspace."
	localPipelineFlagDescription     = "Only show pipelines in the workspace."

	// Run local
	envVarOverrideFlagDescription = `Optional. Override environment variables passed to containers.
Format: [container]:KEY=VALUE. Omit container name to apply to all containers.`
	portOverridesFlagDescription = `Optional. Override ports exposed by service. Format: <host port>:<service port>.
Example: --port-override 5000:80 binds localhost:5000 to the service's port 80.`
	proxyFlagDescription        = `Optional. Proxy outbound requests to your environment's VPC.`
	proxyNetworkFlagDescription = `Optional. Set the IP Network used by --proxy.`
	watchFlagDescription        = `Optional. Watch changes to local files and restart containers when updated. Directories and files in the main .dockerignore file are ignored.`
	useTaskRoleFlagDescription  = "Optional. Run containers with TaskRole credentials instead of session credentials."

	svcManifestFlagDescription = `Optional. Name of the environment in which the service was deployed;
output the manifest file used for that deployment.`
	manifestFlagDescription = "Optional. Output the manifest file used for the deployment."

	execYesFlagDescription     = "Optional. Whether to update the Session Manager Plugin."
	taskIDFlagDescription      = "Optional. ID of the task you want to exec in."
	execCommandFlagDescription = `Optional. The command that is passed to a running container.`
	containerFlagDescription   = "Optional. The specific container you want to exec in. By default the first essential container will be used."

	// Build.
	imageTagFlagDescription     = `Optional. The tag for the container images Copilot builds from Dockerfiles.`
	uploadAssetsFlagDescription = `Optional. Whether to upload assets (container images, Lambda functions, etc.).
Uploaded asset locations are filled in the template configuration.`
	stackOutputDirFlagDescription = "Optional. Writes the stack template and template configuration to a directory."

	// CI/CD.
	pipelineFlagDescription          = "Name of the pipeline."
	githubURLFlagDescription         = "(Deprecated.) Use '--url' instead. Repository URL to trigger your pipeline."
	githubAccessTokenFlagDescription = "GitHub personal access token for your repository."
	gitBranchFlagDescription         = "Branch used to trigger your pipeline."
	pipelineEnvsFlagDescription      = "Environments to add to the pipeline."
	pipelineTypeFlagDescription      = `The type of pipeline. Must be either "Workloads" or "Environments".`

	// Storage.
	storageFlagDescription             = "Name of the storage resource to create."
	storageWorkloadFlagDescription     = "Name of the service/job that accesses the storage."
	storagePartitionKeyFlagDescription = `Partition key for the DDB table.
Must be of the format '<keyName>:<dataType>'.`
	storageSortKeyFlagDescription = `Optional. Sort key for the DDB table.
Must be of the format '<keyName>:<dataType>'.`
	storageNoSortFlagDescription    = "Optional. Skip configuring sort keys."
	storageNoLSIFlagDescription     = `Optional. Don't ask about configuring alternate sort keys.`
	storageLSIConfigFlagDescription = `Optional. Attribute to use as an alternate sort key. May be specified up to 5 times.
Must be of the format '<keyName>:<dataType>'.`
	storageAuroraServerlessVersionFlagDescription = `Optional. Aurora Serverless version.
With "environment" lifecycle, use "v2". 
With "workload" lifecycle, use "v1" or "v2".
`
	storageRDSEngineFlagDescription = `The database engine used in the cluster.
Must be either "MySQL" or "PostgreSQL".`
	storageRDSInitialDBFlagDescription      = "The initial database to create in the cluster."
	storageRDSParameterGroupFlagDescription = "Optional. The name of the parameter group to associate with the cluster."

	// One-off tasks.
	countFlagDescription         = "Optional. The number of tasks to set up."
	cpuFlagDescription           = "Optional. The number of CPU units to reserve for each task."
	memoryFlagDescription        = "Optional. The amount of memory to reserve in MiB for each task."
	taskRoleFlagDescription      = "Optional. The ARN of the role for the task to use."
	executionRoleFlagDescription = "Optional. The ARN of the role that grants the container agent permission to make AWS API calls."
	envVarsFlagDescription       = "Optional. Environment variables specified by key=value separated by commas."
	envFileFlagDescription       = `Optional. A path to an environment variable (.env) file. 
Each line should be of the form of VARIABLE=VALUE. 
Values specified with --env-vars take precedence over --env-file.`
	secretsFlagDescription    = "Optional. Secrets to inject into the container. Specified by key=value separated by commas."
	runCommandFlagDescription = `Optional. The command that is passed to "docker run" to override the default command.`
	entrypointFlagDescription = `Optional. The entrypoint that is passed to "docker run" to override the default entrypoint.`
	taskGroupFlagDescription  = `Optional. The group name of the task. 
Tasks with the same group name share the same set of resources. 
(default directory name)`
	taskImageTagFlagDescription    = `Optional. The container image tag in addition to "latest".`
	generateCommandFlagDescription = `Optional. Generate a command with a pre-filled value for each flag.
To use it for an ECS service, specify --generate-cmd <cluster name>/<service name>.
Alternatively, if the service or job is created with Copilot, specify --generate-cmd <application>/<environment>/<service or job name>.
Cannot be specified with any other flags.`

	// Environment configurations.
	vpcIDFlagDescription              = "Optional. Use an existing VPC ID."
	publicSubnetsFlagDescription      = "Optional. Use existing public subnet IDs."
	privateSubnetsFlagDescription     = "Optional. Use existing private subnet IDs."
	certsFlagDescription              = "Optional. Apply existing ACM certificates to the internet-facing load balancer."
	internalALBSubnetsFlagDescription = `Optional. Specify subnet IDs for an internal load balancer.
By default, the load balancer will be placed in your private subnets.
Cannot be specified with --default-config or any of the --override flags.`
	allowVPCIngressFlagDescription = `Optional. Allow internal ALB ingress from port 80 and/or port 443.`
	overrideVPCCIDRFlagDescription = `Optional. Global CIDR to use for VPC.
(default 10.0.0.0/16)`
	overrideAZsFlagDescription = `Optional. Availability Zone names.
(default 2 random AZs)`
	overridePublicSubnetCIDRsFlagDescription = `Optional. CIDR to use for public subnets. 
(default 10.0.0.0/24,10.0.1.0/24)`
	overridePrivateSubnetCIDRsFlagDescription = `Optional. CIDR to use for private subnets.
(default 10.0.2.0/24,10.0.3.0/24)`

	enableContainerInsightsFlagDescription = "Optional. Enable CloudWatch Container Insights."
	defaultConfigFlagDescription           = "Optional. Skip prompting and use default environment configuration."

	profileFlagDescription         = "Name of the profile for the environment account."
	accessKeyIDFlagDescription     = "Optional. An AWS access key for the environment account."
	secretAccessKeyFlagDescription = "Optional. An AWS secret access key for the environment account."
	sessionTokenFlagDescription    = "Optional. An AWS session token for temporary credentials."
	envRegionTokenFlagDescription  = "Optional. An AWS region where the environment will be created."

	// Other.
	domainNameFlagDescription      = "Optional. Your existing custom domain name."
	deleteSecretFlagDescription    = "Deletes AWS Secrets Manager secret associated with a pipeline source repository."
	svcPortFlagDescription         = "The port on which your service listens."
	noSubscriptionFlagDescription  = "Optional. Turn off selection for adding subscriptions for worker services."
	subscribeTopicsFlagDescription = `Optional. SNS topics to subscribe to from other services in your application.
Must be of format '<svcName>:<topicName>'.`
	retriesFlagDescription = "Optional. The number of times to try restarting the job on a failure."
	timeoutFlagDescription = `Optional. The total execution time for the task, including retries.
Accepts valid Go duration strings. For example: "2h", "1h30m", "900s".`
	scheduleFlagDescription = `The schedule on which to run this job. 
Accepts cron expressions of the format (M H DoM M DoW) and schedule definition strings. 
For example: "0 * * * *", "@daily", "@weekly", "@every 1h30m".
AWS Schedule Expressions of the form "rate(10 minutes)" or "cron(0 12 L * ? 2021)"
are also accepted.`
	upgradeAllEnvsDescription          = "Optional. Upgrade all environments."
	secretOverwriteFlagDescription     = "Optional. Whether to overwrite an existing secret."
	permissionsBoundaryFlagDescription = `Optional. The name or ARN of an existing IAM policy with which to set a
permissions boundary for all roles generated within the application.`
	prodEnvFlagDescription    = "If the environment contains production services."
	deployEnvFlagDescription  = "Deploy the target environment before deploying the workload."
	yesInitEnvFlagDescription = "Confirm initializing the target environment if it does not exist."
)

type portOverride struct {
	host      string
	container string
}

type portOverrides []portOverride

func (p *portOverrides) Set(val string) error {
	err := errors.New("should be in format 8080:80")
	split := strings.Split(val, ":")
	if len(split) != 2 {
		return err
	}
	if _, ok := strconv.Atoi(split[0]); ok != nil {
		return err
	}
	if _, ok := strconv.Atoi(split[1]); ok != nil {
		return err
	}

	*p = append(*p, portOverride{
		host:      split[0],
		container: split[1],
	})
	return nil
}

func (p *portOverrides) Type() string {
	return "list"
}

func (p *portOverrides) String() string {
	return fmt.Sprintf("%+v", *p)
}
