{"record":{"id":"7c6a3f790d240139","repo":"docker/cli","slug":"the-external-ca-option-needs-a-protocol-parameter","errorCode":null,"errorMessage":"the external-ca option needs a protocol= parameter","messagePattern":"the external-ca option needs a protocol= parameter","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/command/swarm/opts.go","lineNumber":210,"sourceCode":"\t\tcase \"url\":\n\t\t\thasURL = true\n\t\t\texternalCA.URL = value\n\t\tcase \"cacert\":\n\t\t\tcacontents, err := os.ReadFile(value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"unable to read CA cert for external CA: %w\", err)\n\t\t\t}\n\t\t\tif pemBlock, _ := pem.Decode(cacontents); pemBlock == nil {\n\t\t\t\treturn nil, errors.New(\"CA cert for external CA must be in PEM format\")\n\t\t\t}\n\t\t\texternalCA.CACert = string(cacontents)\n\t\tdefault:\n\t\t\texternalCA.Options[key] = value\n\t\t}\n\t}\n\n\tif !hasProtocol {\n\t\treturn nil, errors.New(\"the external-ca option needs a protocol= parameter\")\n\t}\n\tif !hasURL {\n\t\treturn nil, errors.New(\"the external-ca option needs a url= parameter\")\n\t}\n\n\treturn &externalCA, nil\n}\n\nfunc addSwarmCAFlags(flags *pflag.FlagSet, options *swarmCAOptions) {\n\tflags.DurationVar(&options.nodeCertExpiry, flagCertExpiry, 90*24*time.Hour, \"Validity period for node certificates (ns|us|ms|s|m|h)\")\n\tflags.Var(&options.externalCA, flagExternalCA, \"Specifications of one or more certificate signing endpoints\")\n}\n\nfunc addSwarmFlags(flags *pflag.FlagSet, options *swarmOptions) {\n\tflags.Int64Var(&options.taskHistoryLimit, flagTaskHistoryLimit, 5, \"Task history retention limit\")\n\tflags.DurationVar(&options.dispatcherHeartbeat, flagDispatcherHeartbeat, 5*time.Second, \"Dispatcher heartbeat period (ns|us|ms|s|m|h)\")\n\tflags.Uint64Var(&options.maxSnapshots, flagMaxSnapshots, 0, \"Number of additional Raft snapshots to retain\")\n\tflags.SetAnnotation(flagMaxSnapshots, \"version\", []string{\"1.25\"})","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/docker/cli/blob/4f84911bfe8811e9b028e4b1fee8e7510be79387/cli/command/swarm/opts.go#L192-L228","documentation":"Thrown by parseExternalCA (cli/command/swarm/opts.go:210) when an `--external-ca` specification contains no `protocol=` key. An external CA spec is incomplete without a protocol (currently only 'cfssl' is recognized), so parsing aborts after the field loop if hasProtocol is false.","triggerScenarios":"Passing `--external-ca url=https://ca.example.com` (protocol omitted) or `--external-ca cacert=/x` without a protocol key.","commonSituations":"Forgot the protocol key; assumed a default; malformed spec string from a template.","solutions":["Add `protocol=cfssl` to the spec: `--external-ca protocol=cfssl,url=https://ca.example.com`.","Validate the spec string contains `protocol=` before invoking the command."],"exampleFix":"// before\ndocker swarm update --external-ca url=https://ca.example.com\n\n// after\ndocker swarm update --external-ca protocol=cfssl,url=https://ca.example.com","handlingStrategy":"validation","validationCode":"// Require protocol= in the external-ca spec string.\nif !strings.Contains(spec, \"protocol=\") {\n\treturn errors.New(\"external-ca spec needs protocol= (e.g. cfssl)\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Build external-ca specs from a struct with required fields rather than raw strings.","Unit-test the spec formatter against missing keys.","Document the mandatory protocol= and url= keys together."],"tags":["docker","swarm","external-ca","validation"],"backgroundTag":null,"analyzedSha":"4f84911bfe8811e9b028e4b1fee8e7510be79387","analyzedAt":"2026-08-07T12:15:29.814Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}