{"record":{"id":"26cedbe00018b8bd","repo":"kubernetes/kops","slug":"invalid-s3-location","errorCode":null,"errorMessage":"invalid S3 location","messagePattern":"invalid S3 location","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/model/resources/nodeup.go","lineNumber":289,"sourceCode":"\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t\tescaped, err := escape(location)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"escaping nodeup source %q: %w\", location, err)\n\t\t}\n\t\tlocations[i] = escaped\n\t}\n\treturn strings.Join(locations, \",\"), nil\n}\n\nfunc escapeS3Location(location string) (string, error) {\n\tu, err := url.Parse(location)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"parsing S3 location: %w\", err)\n\t}\n\tif u.Scheme != \"s3\" || u.Host == \"\" {\n\t\treturn \"\", fmt.Errorf(\"invalid S3 location\")\n\t}\n\n\treturn \"s3://\" + u.Host + httpbinding.EscapePath(u.Path, false), nil\n}\n\nfunc escapeBlobLocation(location string) (string, error) {\n\tu, err := url.Parse(location)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"parsing Azure Blob location: %w\", err)\n\t}\n\tcontainer, key, _ := strings.Cut(strings.TrimPrefix(u.Path, \"/\"), \"/\")\n\t// Reject ports, IPv6 hosts, userinfo, queries, and fragments, which the account-based\n\t// blob.core.windows.net URL cannot represent, so they fail here instead of in the boot retry loop.\n\tif u.Scheme != \"azureblob\" || u.Host == \"\" || u.Hostname() != u.Host || u.User != nil || u.RawQuery != \"\" || u.Fragment != \"\" || container == \"\" || key == \"\" {\n\t\treturn \"\", fmt.Errorf(\"invalid Azure Blob location; expected azureblob://<account>/<container>/<key>\")\n\t}\n\n\treturn \"azureblob://\" + u.Host + httpbinding.EscapePath(u.Path, false), nil","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/model/resources/nodeup.go#L271-L307","documentation":"escapeS3Location requires the parsed URL to have exactly the scheme 's3' and a non-empty host (the bucket name). If either check fails, it returns this error because an S3 path without a bucket, or with the wrong scheme, cannot be resolved later.","triggerScenarios":"A nodeup source location whose scheme is not exactly 's3' (e.g. 'S3://', 'https://', empty) or which parses with an empty host, e.g. 's3:///path/to/nodeup' or 's3://'.","commonSituations":"Bucket name accidentally dropped during refactoring, using an https URL where an s3:// URL is required, or a bare path like '/path/to/nodeup' passed as a source.","solutions":["Ensure the location starts with exactly s3:// (lowercase) and includes a bucket: s3://bucket/key.","Check for double slashes or a missing bucket after the scheme prefix.","If the bucket is filled from config/flag input, validate it is non-empty before building the source string.","Rebuild the nodeup script after fixing the URL."],"exampleFix":"// before\nNodeUpSource: \"s3:///artifacts/nodeup\"\n// after\nNodeUpSource: \"s3://my-artifacts-bucket/artifacts/nodeup\"","handlingStrategy":"validation","validationCode":"u, _ := url.Parse(location)\nif u.Scheme != \"s3\" || u.Host == \"\" {\n    return errors.New(\"location must be s3://<bucket>/<key>\")\n}","typeGuard":"func isS3Location(loc string) bool {\n    u, err := url.Parse(loc)\n    return err == nil && u.Scheme == \"s3\" && u.Host != \"\"\n}","tryCatchPattern":"if !isS3Location(loc) {\n    return fmt.Errorf(\"%q must include bucket: s3://bucket/key\", loc)\n}","preventionTips":["Always include the bucket in s3:// URLs (never s3:///path)","Use lowercase s3 scheme exactly","Add config linting that checks source URL schemes"],"tags":["s3","validation","nodeup"],"backgroundTag":"invalid-url-location","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}