Hello,
Thank you in advance for helping me with this issue. I have been getting SignatureDoesNotMatch error since updating to aws-sdk-js 2.4.* when trying to utilize the createBucket for s3. The following is my javascript code:
var AWS = require('aws-sdk');
AWS.config.region = "us-west-1";
s3bucket = new AWS.S3();
s3bucket.createBucket({Bucket: myNumberVarialbe + "/"},function(err,data){
if (err) {
console.log("Error creating object " + emailId);
console.log("Request")
console.log(this.request.httpRequest)
console.log("Response")
console.log(this.httpResponse)
console.log("Error Object")
console.log(err);
console.log("Data Object")
console.log(data);
}else{
console.log("Object " + emailId + " created.");
callback();
}
});
My AWS configuration is being determined by environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. I have set the IAM user associated with the credentials to be in a group with AmazonS3FullAccess permissions. I have tried using new access key id and secret access key with the user, but I am getting the same errors.
The following is a result of console.log(this.request.httpRequest):
HttpRequest {
method: 'PUT',
path: '/marcom-meb%2F2016%2F833%2F',
headers:
{ 'User-Agent': 'aws-sdk-nodejs/2.4.2 darwin/v5.10.1',
'Content-Type': 'application/octet-stream',
'Content-Length': 153,
Host: 's3-us-west-1.amazonaws.com',
'X-Amz-Content-Sha256': 'UNSIGNED-PAYLOAD',
'X-Amz-Date': '20160625T152044Z',
Authorization: 'AWS4-HMAC-SHA256 Credential=AKIAIPVWN52OHEY7IFCA/20160625/us-west-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=a0c8c183ed529e36d268a4df4b9396144fc225c39568f37d89d1888fa12286c6' },
body: '<CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><LocationConstraint>us-west-1</LocationConstraint></CreateBucketConfiguration>',
endpoint:
Endpoint {
protocol: 'https:',
host: 's3-us-west-1.amazonaws.com',
port: 443,
hostname: 's3-us-west-1.amazonaws.com',
pathname: '/',
path: '/',
href: 'https://s3-us-west-1.amazonaws.com/',
constructor: { [Function: Endpoint] __super__: [Function: Object] } },
region: 'us-west-1',
stream:
ClientRequest {
domain: null,
_events:
{ timeout: [Object],
error: [Function],
sendProgress: [Function: onSendProgress],
receiveProgress: [Function: onReceiveProgress] },
_eventsCount: 4,
_maxListeners: undefined,
output: [],
outputEncodings: [],
outputCallbacks: [],
outputSize: 0,
writable: true,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: true,
sendDate: false,
_removedHeader: { 'content-length': false },
_contentLength: 153,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket:
TLSSocket {
_tlsOptions: [Object],
_secureEstablished: true,
_securePending: false,
_newSessionPending: false,
_controlReleased: true,
_SNICallback: null,
servername: null,
npnProtocol: undefined,
alpnProtocol: false,
authorized: true,
authorizationError: null,
encrypted: true,
_events: [Object],
_eventsCount: 10,
_connecting: false,
_hadError: false,
_handle: null,
_parent: null,
_host: 's3-us-west-1.amazonaws.com',
_readableState: [Object],
readable: false,
domain: null,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
destroyed: true,
bytesRead: 2203,
_bytesDispatched: 658,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: undefined,
_server: null,
ssl: null,
_requestCert: true,
_rejectUnauthorized: true,
parser: null,
_httpMessage: [Circular],
_idleTimeout: -1,
_idleNext: null,
_idlePrev: null,
_idleStart: 4032,
read: [Function],
_consuming: true },
connection:
TLSSocket {
_tlsOptions: [Object],
_secureEstablished: true,
_securePending: false,
_newSessionPending: false,
_controlReleased: true,
_SNICallback: null,
servername: null,
npnProtocol: undefined,
alpnProtocol: false,
authorized: true,
authorizationError: null,
encrypted: true,
_events: [Object],
_eventsCount: 10,
_connecting: false,
_hadError: false,
_handle: null,
_parent: null,
_host: 's3-us-west-1.amazonaws.com',
_readableState: [Object],
readable: false,
domain: null,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
destroyed: true,
bytesRead: 2203,
_bytesDispatched: 658,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: undefined,
_server: null,
ssl: null,
_requestCert: true,
_rejectUnauthorized: true,
parser: null,
_httpMessage: [Circular],
_idleTimeout: -1,
_idleNext: null,
_idlePrev: null,
_idleStart: 4032,
read: [Function],
_consuming: true },
_header: 'PUT /marcom-meb%2F2016%2F833%2F HTTP/1.1\r\nUser-Agent: aws-sdk-nodejs/2.4.2 darwin/v5.10.1\r\nContent-Type: application/octet-stream\r\nContent-Length: 153\r\nHost: s3-us-west-1.amazonaws.com\r\nX-Amz-Content-Sha256: UNSIGNED-PAYLOAD\r\nX-Amz-Date: 20160625T152044Z\r\nAuthorization: AWS4-HMAC-SHA256 Credential=AKIAIPVWN52OHEY7IFCA/20160625/us-west-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=a0c8c183ed529e36d268a4df4b9396144fc225c39568f37d89d1888fa12286c6\r\nConnection: close\r\n\r\n',
_headers:
{ 'user-agent': 'aws-sdk-nodejs/2.4.2 darwin/v5.10.1',
'content-type': 'application/octet-stream',
'content-length': 153,
host: 's3-us-west-1.amazonaws.com',
'x-amz-content-sha256': 'UNSIGNED-PAYLOAD',
'x-amz-date': '20160625T152044Z',
authorization: 'AWS4-HMAC-SHA256 Credential=AKIAIPVWN52OHEY7IFCA/20160625/us-west-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=a0c8c183ed529e36d268a4df4b9396144fc225c39568f37d89d1888fa12286c6' },
_headerNames:
{ 'user-agent': 'User-Agent',
'content-type': 'Content-Type',
'content-length': 'Content-Length',
host: 'Host',
'x-amz-content-sha256': 'X-Amz-Content-Sha256',
'x-amz-date': 'X-Amz-Date',
authorization: 'Authorization' },
_onPendingData: null,
agent:
Agent {
domain: null,
_events: [Object],
_eventsCount: 1,
_maxListeners: 0,
defaultPort: 443,
protocol: 'https:',
options: [Object],
requests: {},
sockets: [Object],
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: [Getter],
maxFreeSockets: 256,
maxCachedSessions: 100,
_sessionCache: [Object] },
socketPath: undefined,
method: 'PUT',
path: '/marcom-meb%2F2016%2F833%2F',
timeoutCb: [Function: emitTimeout],
parser: null,
res:
IncomingMessage {
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_eventsCount: 3,
_maxListeners: undefined,
socket: [Object],
connection: [Object],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
headers: [Object],
rawHeaders: [Object],
trailers: {},
rawTrailers: [],
upgrade: false,
url: '',
method: null,
statusCode: 403,
statusMessage: 'Forbidden',
client: [Object],
_consuming: true,
_dumped: false,
req: [Circular],
read: [Function] } } }
The following is a result of console.log(this.httpResponse):
HttpResponse {
statusCode: 403,
headers:
{ 'x-amz-request-id': '825A85F77F095AED',
'x-amz-id-2': 'tDTo5Bc0CekwHp1LnVB6kYs4PyY0XBVY3+HNhYs6E6YoMV5vuY02xPGEe3k3ZJ6DeS8qIZS7PvQ=',
'content-type': 'application/xml',
'transfer-encoding': 'chunked',
date: 'Sat, 25 Jun 2016 15:20:44 GMT',
connection: 'close',
server: 'AmazonS3' },
body: <Buffer 3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d 22 31 2e 30 22 20 65 6e 63 6f 64 69 6e 67 3d 22 55 54 46 2d 38 22 3f 3e 0a 3c 45 72 72 6f 72 3e 3c 43 6f 64 ... >,
streaming: false,
stream:
IncomingMessage {
_readableState:
ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: [],
length: 0,
pipes: null,
pipesCount: 0,
flowing: null,
ended: true,
endEmitted: true,
reading: false,
sync: false,
needReadable: false,
emittedReadable: false,
readableListening: true,
resumeScheduled: false,
defaultEncoding: 'utf8',
ranOut: false,
awaitDrain: 0,
readingMore: false,
decoder: null,
encoding: null },
readable: false,
domain: null,
_events:
{ end: [Object],
headers: [Function: onHeaders],
readable: [Function: onReadable] },
_eventsCount: 3,
_maxListeners: undefined,
socket:
TLSSocket {
_tlsOptions: [Object],
_secureEstablished: true,
_securePending: false,
_newSessionPending: false,
_controlReleased: true,
_SNICallback: null,
servername: null,
npnProtocol: undefined,
alpnProtocol: false,
authorized: true,
authorizationError: null,
encrypted: true,
_events: [Object],
_eventsCount: 10,
_connecting: false,
_hadError: false,
_handle: null,
_parent: null,
_host: 's3-us-west-1.amazonaws.com',
_readableState: [Object],
readable: false,
domain: null,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
destroyed: true,
bytesRead: 2203,
_bytesDispatched: 658,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: undefined,
_server: null,
ssl: null,
_requestCert: true,
_rejectUnauthorized: true,
parser: null,
_httpMessage: [Object],
_idleTimeout: -1,
_idleNext: null,
_idlePrev: null,
_idleStart: 4032,
read: [Function],
_consuming: true },
connection:
TLSSocket {
_tlsOptions: [Object],
_secureEstablished: true,
_securePending: false,
_newSessionPending: false,
_controlReleased: true,
_SNICallback: null,
servername: null,
npnProtocol: undefined,
alpnProtocol: false,
authorized: true,
authorizationError: null,
encrypted: true,
_events: [Object],
_eventsCount: 10,
_connecting: false,
_hadError: false,
_handle: null,
_parent: null,
_host: 's3-us-west-1.amazonaws.com',
_readableState: [Object],
readable: false,
domain: null,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
destroyed: true,
bytesRead: 2203,
_bytesDispatched: 658,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: undefined,
_server: null,
ssl: null,
_requestCert: true,
_rejectUnauthorized: true,
parser: null,
_httpMessage: [Object],
_idleTimeout: -1,
_idleNext: null,
_idlePrev: null,
_idleStart: 4032,
read: [Function],
_consuming: true },
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
headers:
{ 'x-amz-request-id': '825A85F77F095AED',
'x-amz-id-2': 'tDTo5Bc0CekwHp1LnVB6kYs4PyY0XBVY3+HNhYs6E6YoMV5vuY02xPGEe3k3ZJ6DeS8qIZS7PvQ=',
'content-type': 'application/xml',
'transfer-encoding': 'chunked',
date: 'Sat, 25 Jun 2016 15:20:44 GMT',
connection: 'close',
server: 'AmazonS3' },
rawHeaders:
[ 'x-amz-request-id',
'825A85F77F095AED',
'x-amz-id-2',
'tDTo5Bc0CekwHp1LnVB6kYs4PyY0XBVY3+HNhYs6E6YoMV5vuY02xPGEe3k3ZJ6DeS8qIZS7PvQ=',
'Content-Type',
'application/xml',
'Transfer-Encoding',
'chunked',
'Date',
'Sat, 25 Jun 2016 15:20:44 GMT',
'Connection',
'close',
'Server',
'AmazonS3' ],
trailers: {},
rawTrailers: [],
upgrade: false,
url: '',
method: null,
statusCode: 403,
statusMessage: 'Forbidden',
client:
TLSSocket {
_tlsOptions: [Object],
_secureEstablished: true,
_securePending: false,
_newSessionPending: false,
_controlReleased: true,
_SNICallback: null,
servername: null,
npnProtocol: undefined,
alpnProtocol: false,
authorized: true,
authorizationError: null,
encrypted: true,
_events: [Object],
_eventsCount: 10,
_connecting: false,
_hadError: false,
_handle: null,
_parent: null,
_host: 's3-us-west-1.amazonaws.com',
_readableState: [Object],
readable: false,
domain: null,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
destroyed: true,
bytesRead: 2203,
_bytesDispatched: 658,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: undefined,
_server: null,
ssl: null,
_requestCert: true,
_rejectUnauthorized: true,
parser: null,
_httpMessage: [Object],
_idleTimeout: -1,
_idleNext: null,
_idlePrev: null,
_idleStart: 4032,
read: [Function],
_consuming: true },
_consuming: true,
_dumped: false,
req:
ClientRequest {
domain: null,
_events: [Object],
_eventsCount: 4,
_maxListeners: undefined,
output: [],
outputEncodings: [],
outputCallbacks: [],
outputSize: 0,
writable: true,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: true,
sendDate: false,
_removedHeader: [Object],
_contentLength: 153,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: [Object],
connection: [Object],
_header: 'PUT /marcom-meb%2F2016%2F833%2F HTTP/1.1\r\nUser-Agent: aws-sdk-nodejs/2.4.2 darwin/v5.10.1\r\nContent-Type: application/octet-stream\r\nContent-Length: 153\r\nHost: s3-us-west-1.amazonaws.com\r\nX-Amz-Content-Sha256: UNSIGNED-PAYLOAD\r\nX-Amz-Date: 20160625T152044Z\r\nAuthorization: AWS4-HMAC-SHA256 Credential=AKIAIPVWN52OHEY7IFCA/20160625/us-west-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=a0c8c183ed529e36d268a4df4b9396144fc225c39568f37d89d1888fa12286c6\r\nConnection: close\r\n\r\n',
_headers: [Object],
_headerNames: [Object],
_onPendingData: null,
agent: [Object],
socketPath: undefined,
method: 'PUT',
path: '/marcom-meb%2F2016%2F833%2F',
timeoutCb: [Function: emitTimeout],
parser: null,
res: [Circular] },
read: [Function] },
_abortCallback: [Function: callNextListener] }
The following is the result of console.log(err);
{ [SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.]
message: 'The request signature we calculated does not match the signature you provided. Check your key and signing method.',
code: 'SignatureDoesNotMatch',
region: null,
time: Sat Jun 25 2016 08:20:45 GMT-0700 (PDT),
requestId: '825A85F77F095AED',
extendedRequestId: 'tDTo5Bc0CekwHp1LnVB6kYs4PyY0XBVY3+HNhYs6E6YoMV5vuY02xPGEe3k3ZJ6DeS8qIZS7PvQ=',
cfId: undefined,
statusCode: 403,
retryable: false,
retryDelay: 9.48646585457027 }
The result of console.log(data); is null
Any help or direction you can could provide would be greatly appreciated. Thank you!
@paswicka
It looks like the issue is you have a / in your bucket name. That is only valid with object names.
Prior to 2.4.x, the S3 client was configured to use signatureVersion: v2 by default for regions that supported v2 and v4. Using v2, the / was being dropped, but not with v4.
In this case, I'd recommend removing the / from your bucket name since it isn't a valid symbol and was previously being dropped.
Thank you @chrisradek for your response and direction. The naming appears to have something to do with the issue since I am still able to create a bucket in the root directory without a problem.
The variable I am using for the bucket I am trying to create (myNumberVariable in the previous example) is actually a path to a bucket within another bucket with a forward slash (/) separating the two names indicating a folder structure. Whenever I try to create a new bucket within another existing bucket using the forward slash (ex: bucket/subBucket, the forward slash (/) gets replaced with the special character code %2F (ex: bucket%2FsubBucket).
This previously worked successfully in v2. I have tried escaping the forward slash in the bucket name with the same results. Is there a different way to create a "sub-bucket" within an existing bucket using v4?
@paswicka
Interesting, I'll have to look into seeing if this is an edge case that happened to work with v2 or if it's working intentionally. As a temporary work-around, you can still tell your S3 client to use v2.
var s3 = new AWS.S3({signatureVersion: 'v2', ...});
Is there a reason you can't use folders within a bucket instead of 'sub-buckets'?
I thought buckets and folders were synonymous. I'd be happy to use folders, though, if I can achieve the same results.
If you create an object with a / in the key, it will treat the key similar to a path.
For example, if I uploaded an object with the key temp/images/temporary_image.png, temp and images would appear to be folders (you can verify in the S3 console).
The obvious benefit to going this route is that each account has a limit to the number of buckets (100) they can have at a given time, but there isn't the same restriction on folders.
Is the only way to create folders by creating an object, or is there a way to create folders without the dependency of creating an object too?
Thanks again!
Through the console, you can create a folder, but programmatically the only way is to create an object. However, you can create an object that is just a folder:
s3.putObject({Bucket: 'bucket', Key: 'my_folder/'}, function(err, data) { /* ... */ });
The above example creates a new folder called my_folder. There's no need to specify a body in this case.
It might help if you think of objects as being folders or files. You can create/access them the same way. Just realize that operations like getObject on a folder won't return the entire directory, it'll return just the information about that folder.
So now that I'm using putObject, I am getting an error for Missing credentials in config.
Code:
s3bucket.putObject({ Bucket: 'marcom-meb', Key: '2016/887/' }, function(err,data){
if (err) {
console.log("Error creating object " + emailId);
console.log("Request")
console.log(this.request.httpRequest)
console.log("Response")
console.log(this.httpResponse)
console.log("Error Object")
console.log(err);
console.log("Data Object")
console.log(data);
}else{
console.log("Object " + emailId + " created.");
callback();
}
});
Request:
HttpRequest {
method: 'POST',
path: '/',
headers: { 'User-Agent': 'aws-sdk-nodejs/2.4.2 darwin/v5.10.1' },
body: '',
endpoint:
Endpoint {
protocol: 'https:',
host: 's3-us-west-1.amazonaws.com',
port: 443,
hostname: 's3-us-west-1.amazonaws.com',
pathname: '/',
path: '/',
href: 'https://s3-us-west-1.amazonaws.com/',
constructor: { [Function: Endpoint] __super__: [Function: Object] } },
region: 'us-west-1' }
Response:
HttpResponse {
statusCode: undefined,
headers: {},
body: undefined,
streaming: false,
stream: null }
Error Object:
{ [TimeoutError: Missing credentials in config]
message: 'Missing credentials in config',
code: 'CredentialsError',
time: Tue Jun 28 2016 11:02:05 GMT-0700 (PDT),
originalError:
{ message: 'Could not load credentials from any providers',
code: 'CredentialsError',
time: Tue Jun 28 2016 11:02:05 GMT-0700 (PDT),
originalError:
{ message: 'Missing credentials in config',
code: 'CredentialsError',
time: Tue Jun 28 2016 11:02:05 GMT-0700 (PDT),
originalError: [Object] } } }
Any ideas? Thank you again!!
How are you configuring your credentials?
By environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. I am also setting the region to "us-west-1" before initializing a new s3 instance.
I've also tried through using AWS_CREDENTIAL_FILE to the csv file with AWSAccessKeyId and AWSSecretKey values.
I just tried using ~/.aws/credentials, and that seems to have worked... however I am dependent on using environment variable for my production setup... Did something change on 2.4.* with this?
Nothing changed with regards to handling credentials with 2.4.x. Can you make sure that your environment variables are accessible to node.js from within your app by inspecting process.env?
Both process.env.PUBLIC_KEY and process.env.PRIVATE_KEY trace out as undefined using AWS_CREDENTIAL_FILE as an environment variable to point to a csv with credentials as well as directly using AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as environment variables. Tracing out in the respective environment variables in the terminal is successful, though.
Are you saying that AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY don't show up on process.env when you specify them either? That would indicate that node.js isn't able to access your environment variables on your machine, so there would be an issue outside of the SDK in that case.
Yes, that's it. I will look into it further. I haven't tried pushing to my testing server yet, so it could be an issue with my local machine.
Thank you so much for all your help! Much appreciated!
Closing this issue. Using #1046 to track updating the SDK to throw a more informative error when using / in a bucket name.
I am getting same issue.
Below is the request and response
Request start
***** HttpRequest*******************************
Request url : https://s3-us-west-1.amazonaws.com/abcd.c/s3Test53519
Request headers :
Accept-Encoding : gzip, deflate
X-Amz-Date : 20160914T141355Z
x-amz-acl : authenticated-read
X-Amz-User-Agent : aws-sdk-js/2.5.3
X-Amz-Content-Sha256 : UNSIGNED-PAYLOAD
x-amz-storage-class : STANDARD
Authorization : AWS4-HMAC-SHA256 Credential=AKIAIAI4MMPGSDH4BOYQ/20160914/us-west-1/s3/aws4_request, SignedHeaders=content-md5;expires;host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-request-payer;x-amz-storage-class;x-amz-user-agent, Signature=a2b2995f01b1be8a5480220f2ab85725a9a2cab0b777cdf3
x-amz-request-payer : requester
Response headers :
Connection : close
Server : AmazonS3
Transfer-Encoding : chunked
x-amz-id-2 : ICUUN0gY+05UkywxL5oTXb6aQQyNXuUJA8uV+u1Yq6BM/fNixuok9jLfYYhKMtg5UWYWfsn4MwI=
Date : Wed, 14 Sep 2016 14:14:23 GMT
x-amz-request-id : 5F7AA212B37B002F
Status : 403
Status-Description : Forbidden
Content-type : Document
Content-length : 2846
Response :
SignatureDoesNotMatch
20160914T141355Z
20160914/us-west-1/s3/aws4_request
cd9374504e7b4de65cd51d6600228eaa41c08c5877eb72844a7f5f1428f82e11
/abcd.c/s3Test53519
content-md5:
expires:
host:s3-us-west-1.amazonaws.com
x-amz-acl:authenticated-read
x-amz-content-sha256:UNSIGNED-PAYLOAD
x-amz-date:20160914T141355Z
x-amz-request-payer:requester
x-amz-storage-class:STANDARD
x-amz-user-agent:aws-sdk-js/2.5.3
content-md5;expires;host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-request-payer;x-amz-storage-class;x-amz-user-agent
UNSIGNED-PAYLOAD
Request end *******************************************
I have give full permission form console.
Can you help me with same?
@padmaja2812
Are you specifying a / when supplying your bucket name?
@chrisradek
No. my bucket name is somthing like xyz.abc
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.
Most helpful comment
Through the console, you can create a folder, but programmatically the only way is to create an object. However, you can create an object that is just a folder:
The above example creates a new folder called
my_folder. There's no need to specify a body in this case.