kopia lustrzana https://github.com/simonw/s3-credentials
rodzic
ad9b4a4a6f
commit
b698fa03da
27
docs/help.md
27
docs/help.md
|
@ -34,6 +34,7 @@ Options:
|
|||
|
||||
Commands:
|
||||
create Create and return new AWS credentials for specified...
|
||||
delete-objects Delete one or more object from an S3 bucket
|
||||
delete-user Delete specified users, their access keys and their...
|
||||
get-cors-policy Get CORS policy for a bucket
|
||||
get-object Download an object from an S3 bucket
|
||||
|
@ -102,6 +103,32 @@ Options:
|
|||
-a, --auth FILENAME Path to JSON/INI file containing credentials
|
||||
--help Show this message and exit.
|
||||
```
|
||||
## s3-credentials delete-objects --help
|
||||
|
||||
```
|
||||
Usage: s3-credentials delete-objects [OPTIONS] BUCKET [KEYS]...
|
||||
|
||||
Delete one or more object from an S3 bucket
|
||||
|
||||
Pass one or more keys to delete them:
|
||||
|
||||
s3-credentials delete-objects my-bucket one.txt two.txt
|
||||
|
||||
To delete all files matching a prefix, pass --prefix:
|
||||
|
||||
s3-credentials delete-objects my-bucket --prefix my-folder/
|
||||
|
||||
Options:
|
||||
--prefix TEXT Delete everything with this prefix
|
||||
-s, --silent Don't show informational output
|
||||
-d, --dry-run Show keys that would be deleted without deleting them
|
||||
--access-key TEXT AWS access key ID
|
||||
--secret-key TEXT AWS secret access key
|
||||
--session-token TEXT AWS session token
|
||||
--endpoint-url TEXT Custom endpoint URL
|
||||
-a, --auth FILENAME Path to JSON/INI file containing credentials
|
||||
--help Show this message and exit.
|
||||
```
|
||||
## s3-credentials delete-user --help
|
||||
|
||||
```
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
# Other commands
|
||||
|
||||
```{contents}
|
||||
---
|
||||
local:
|
||||
class: this-will-duplicate-information-and-it-is-still-useful-here
|
||||
---
|
||||
```
|
||||
|
||||
## policy
|
||||
|
||||
You can use the `s3-credentials policy` command to generate the JSON policy document that would be used without applying it. The command takes one or more required bucket names and a subset of the options available on the `create` command:
|
||||
|
@ -399,6 +406,22 @@ out/alverstone-mead-2.jpg => s3://my-bucket/out/alverstone-mead-2.jpg
|
|||
out/alverstone-mead-1.jpg => s3://my-bucket/out/alverstone-mead-1.jpg
|
||||
```
|
||||
|
||||
## delete-objects
|
||||
|
||||
`s3-credentials delete-objects` can be used to delete one or more keys from the bucket.
|
||||
|
||||
Pass one or more keys to delete them:
|
||||
|
||||
s3-credentials delete-objects my-bucket one.txt two.txt three.txt
|
||||
|
||||
Use `--prefix my-prefix` to delete all keys with the specified prefix:
|
||||
|
||||
s3-credentials delete-objects my-bucket --prefix my-prefix
|
||||
|
||||
Pass `-d` or `--dry-run` to perform a dry-run of the deletion, which will list the keys that would be deleted without actually deleting them.
|
||||
|
||||
s3-credentials delete-objects my-bucket --prefix my-prefix --dry-run
|
||||
|
||||
## get-object
|
||||
|
||||
To download a file from a bucket use `s3-credentials get-object`:
|
||||
|
|
|
@ -1329,6 +1329,75 @@ def get_cors_policy(bucket, **boto_options):
|
|||
click.echo(json.dumps(response["CORSRules"], indent=4, default=str))
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument("bucket")
|
||||
@click.argument(
|
||||
"keys",
|
||||
nargs=-1,
|
||||
)
|
||||
@click.option(
|
||||
"--prefix",
|
||||
help="Delete everything with this prefix",
|
||||
)
|
||||
@click.option(
|
||||
"silent", "-s", "--silent", is_flag=True, help="Don't show informational output"
|
||||
)
|
||||
@click.option(
|
||||
"dry_run",
|
||||
"-d",
|
||||
"--dry-run",
|
||||
is_flag=True,
|
||||
help="Show keys that would be deleted without deleting them",
|
||||
)
|
||||
@common_boto3_options
|
||||
def delete_objects(bucket, keys, prefix, silent, dry_run, **boto_options):
|
||||
"""
|
||||
Delete one or more object from an S3 bucket
|
||||
|
||||
Pass one or more keys to delete them:
|
||||
|
||||
s3-credentials delete-objects my-bucket one.txt two.txt
|
||||
|
||||
To delete all files matching a prefix, pass --prefix:
|
||||
|
||||
s3-credentials delete-objects my-bucket --prefix my-folder/
|
||||
"""
|
||||
s3 = make_client("s3", **boto_options)
|
||||
if keys and prefix:
|
||||
raise click.ClickException("Cannot pass both keys and --prefix")
|
||||
if not keys and not prefix:
|
||||
raise click.ClickException("Specify one or more keys or use --prefix")
|
||||
if prefix:
|
||||
# List all keys with this prefix
|
||||
paginator = s3.get_paginator("list_objects_v2")
|
||||
response_iterator = paginator.paginate(Bucket=bucket, Prefix=prefix)
|
||||
keys = []
|
||||
for response in response_iterator:
|
||||
keys.extend([obj["Key"] for obj in response.get("Contents", [])])
|
||||
if not silent:
|
||||
click.echo(
|
||||
"Deleting {} object{} from {}".format(
|
||||
len(keys), "s" if len(keys) != 1 else "", bucket
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
if dry_run:
|
||||
click.echo("The following keys would be deleted:")
|
||||
for key in keys:
|
||||
click.echo(key)
|
||||
return
|
||||
for batch in batches(keys, 1000):
|
||||
# Remove any rogue \r characters:
|
||||
batch = [k.strip() for k in batch]
|
||||
response = s3.delete_objects(
|
||||
Bucket=bucket, Delete={"Objects": [{"Key": key} for key in batch]}
|
||||
)
|
||||
if response.get("Errors"):
|
||||
click.echo(
|
||||
"Errors deleting objects: {}".format(response["Errors"]), err=True
|
||||
)
|
||||
|
||||
|
||||
def output(iterator, headers, nl, csv, tsv):
|
||||
if nl:
|
||||
for item in iterator:
|
||||
|
@ -1397,3 +1466,7 @@ def format_bytes(size):
|
|||
size /= 1024
|
||||
|
||||
return size
|
||||
|
||||
|
||||
def batches(all, batch_size):
|
||||
return [all[i : i + batch_size] for i in range(0, len(all), batch_size)]
|
||||
|
|
|
@ -1212,3 +1212,67 @@ def test_put_objects(moto_s3, args, expected, expected_output):
|
|||
for obj in moto_s3.list_objects(Bucket="my-bucket").get("Contents") or []
|
||||
}
|
||||
assert keys == (expected or set())
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"args,expected,expected_error",
|
||||
(
|
||||
([], None, "Error: Specify one or more keys or use --prefix"),
|
||||
(
|
||||
["one.txt", "--prefix", "directory/"],
|
||||
None,
|
||||
"Cannot pass both keys and --prefix",
|
||||
),
|
||||
(["one.txt"], ["directory/two.txt", "directory/three.json"], None),
|
||||
(["one.txt", "directory/two.txt"], ["directory/three.json"], None),
|
||||
(["--prefix", "directory/"], ["one.txt"], None),
|
||||
),
|
||||
)
|
||||
def test_delete_objects(moto_s3_populated, args, expected, expected_error):
|
||||
runner = CliRunner(mix_stderr=False)
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(
|
||||
cli, ["delete-objects", "my-bucket"] + args, catch_exceptions=False
|
||||
)
|
||||
if expected_error:
|
||||
assert result.exit_code != 0
|
||||
assert expected_error in result.stderr
|
||||
else:
|
||||
assert result.exit_code == 0, result.output
|
||||
# Check expected files are left in bucket
|
||||
keys = {
|
||||
obj["Key"]
|
||||
for obj in moto_s3_populated.list_objects(Bucket="my-bucket").get(
|
||||
"Contents"
|
||||
)
|
||||
or []
|
||||
}
|
||||
assert keys == set(expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arg", ("-d", "--dry-run"))
|
||||
def test_delete_objects_dry_run(moto_s3_populated, arg):
|
||||
runner = CliRunner(mix_stderr=False)
|
||||
|
||||
def get_keys():
|
||||
return {
|
||||
obj["Key"]
|
||||
for obj in moto_s3_populated.list_objects(Bucket="my-bucket").get(
|
||||
"Contents"
|
||||
)
|
||||
or []
|
||||
}
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
before_keys = get_keys()
|
||||
result = runner.invoke(
|
||||
cli, ["delete-objects", "my-bucket", "--prefix", "directory/", arg]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert result.output == (
|
||||
"The following keys would be deleted:\n"
|
||||
"directory/three.json\n"
|
||||
"directory/two.txt\n"
|
||||
)
|
||||
after_keys = get_keys()
|
||||
assert before_keys == after_keys
|
||||
|
|
Ładowanie…
Reference in New Issue