{"record":{"id":"0b1a67a8831320bd","repo":"kubernetes/kops","slug":"write-to-s-with-acl-of-unexpected-type-t-0b1a67","errorCode":null,"errorMessage":"write to %s with ACL of unexpected type %T","messagePattern":"write to (.+?) with ACL of unexpected type %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/pkg/vfs/s3fs.go","lineNumber":311,"sourceCode":"\t\t\tsseLog = \"AES256\"\n\t\t\tsse = types.ServerSideEncryptionAes256\n\t\t}\n\t}\n\n\treturn sse, sseLog, nil\n}\n\nfunc (p *S3Path) getRequestACL(aclObj ACL) (*types.ObjectCannedACL, error) {\n\tacl := os.Getenv(\"KOPS_STATE_S3_ACL\")\n\tacl = strings.TrimSpace(acl)\n\tif acl != \"\" {\n\t\tcannedACL := types.ObjectCannedACL(acl)\n\t\tklog.V(8).Infof(\"Using KOPS_STATE_S3_ACL=%s\", acl)\n\t\treturn &cannedACL, nil\n\t} else if aclObj != nil {\n\t\ts3Acl, ok := aclObj.(*S3Acl)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"write to %s with ACL of unexpected type %T\", p, aclObj)\n\t\t}\n\t\treturn s3Acl.RequestACL, nil\n\t}\n\treturn nil, nil\n}\n\nfunc (p *S3Path) WriteFile(ctx context.Context, data io.ReadSeeker, aclObj ACL) error {\n\tctx, span := tracer.Start(ctx, \"S3Path::WriteFile\", trace.WithAttributes(attribute.String(\"path\", p.String())))\n\tdefer span.End()\n\n\tclient, err := p.client(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tklog.V(4).Infof(\"Writing file %q\", p)\n\n\trequest := &s3.PutObjectInput{}","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/util/pkg/vfs/s3fs.go#L293-L329","documentation":"getRequestACL converts the vfs ACL option into an S3 request ACL. If the ACL passed via vfs ACL option is not *S3Acl (and not a plain canned-ACL string), the write is aborted with \"write to <path> with ACL of unexpected type %T\". This is a programming error: the caller supplied an ACL type meant for a different backend.","triggerScenarios":"Calling WriteFile (directly or via CreateFile) with an ACL option set to e.g. *GSAcl, *azure ACL type, or any non-*S3Acl object while writing to an S3 path.","commonSituations":"Generic tooling that applies the same vfs.ACL option across cloud backends; copy-pasted code passing a GCS ACL to an S3 state store; custom vfs wrappers constructing the wrong ACL struct.","solutions":["Pass *vfs.S3Acl (constructed with the desired types.ObjectCannedACL) when writing to S3 paths","Alternatively pass the canned ACL as a plain string (handled by the KOPS_STATE_S3_ACL string branch) or set the KOPS_STATE_S3_ACL env var and omit the option","Audit generic write helpers to select the backend-specific ACL type based on the path type"],"exampleFix":"// before\nerr = vfs.Context.WriteFile(p, data, vfsopt.WithACL(gcutil.NewAcl(\"project-owner\")))\n// after\nerr = vfs.Context.WriteFile(p, data, vfsopt.WithACL(vfs.NewS3Acl(\"private\")))","handlingStrategy":"validation","validationCode":"switch acl := opt.(type) {\ncase *vfs.S3Acl:\n\t// ok\ncase string:\n\t// ok, canned ACL\ndefault:\n\treturn fmt.Errorf(\"backend %T needs a backend-specific ACL, got %T\", p, opt)\n}","typeGuard":"func isS3Acl(a any) bool { _, ok := a.(*vfs.S3Acl); return ok }","tryCatchPattern":"if err := vfs.Context.WriteFile(p, data, opts...); err != nil {\n\tif strings.Contains(err.Error(), \"ACL of unexpected type\") {\n\t\treturn rebuildOptionsForBackend(p, data)\n\t}\n\treturn err\n}","preventionTips":["Match ACL option type to the path backend (S3Acl for S3, GSAcl for GCS)","Prefer KOPS_STATE_S3_ACL env var over code-supplied ACL structs","Centralize ACL option construction in backend-aware helpers"],"tags":["s3","acl","type-mismatch","api-misuse"],"backgroundTag":"acl-type-mismatch","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}