diff --git a/astraSDK/protections.py b/astraSDK/protections.py index ac3216c..fcec280 100644 --- a/astraSDK/protections.py +++ b/astraSDK/protections.py @@ -180,6 +180,7 @@ def main( minute, appID, recurrenceRule=None, + bucketID=None, ): endpoint = f"k8s/v1/apps/{appID}/schedules" url = self.base + endpoint @@ -200,6 +201,8 @@ def main( if recurrenceRule: data["recurrenceRule"] = recurrenceRule data["replicate"] = "true" + if bucketID: + data["bucketID"] = bucketID ret = super().apicall( "post", @@ -223,6 +226,76 @@ def main( return False +class updateProtectionpolicy(SDKCommon): + """Update a protection policy. This class does no validation of the arguments, leaving + that to the API call itself. tkSrc/update.py can be used as a guide as to what the API + requirements are in case the swagger isn't sufficient. + """ + + def __init__(self, quiet=True, verbose=False): + """quiet: Will there be CLI output or just return (datastructure) + verbose: Print all of the ReST call info: URL, Method, Headers, Request Body""" + self.quiet = quiet + self.verbose = verbose + super().__init__() + self.headers["Content-Type"] = "application/astra-schedule+json" + + def main( + self, + appID, + protectionID, + granularity, + backupRetention, + snapshotRetention, + minute=None, + hour=None, + dayOfWeek=None, + dayOfMonth=None, + bucketID=None, + ): + endpoint = f"k8s/v1/apps/{appID}/schedules/{protectionID}" + url = self.base + endpoint + params = {} + data = { + "type": "application/astra-schedule", + "version": "1.3", + "granularity": granularity, + "backupRetention": backupRetention, + "snapshotRetention": snapshotRetention, + } + if minute: + data["minute"] = minute + if hour: + data["hour"] = hour + if dayOfWeek: + data["dayOfWeek"] = dayOfWeek + if dayOfMonth: + data["dayOfMonth"] = dayOfMonth + if bucketID: + data["bucketID"] = bucketID + + ret = super().apicall( + "put", + url, + data, + self.headers, + params, + self.verifySSL, + quiet=self.quiet, + verbose=self.verbose, + ) + + if ret.ok: + results = super().jsonifyResults(ret) + if not self.quiet: + print(json.dumps(results)) + return results + else: + if not self.quiet: + super().printError(ret) + return False + + class destroyProtectiontionpolicy(SDKCommon): """This class destroys a protection policy""" diff --git a/docs/toolkit/update/README.md b/docs/toolkit/update/README.md index 20e153f..28ff146 100644 --- a/docs/toolkit/update/README.md +++ b/docs/toolkit/update/README.md @@ -5,21 +5,24 @@ The `update` argument allows you to update an Astra resource, at this time only * [Bucket](#bucket) * [Cloud](#cloud) * [Cluster](#cluster) +* [Protection](#protection) * [Replication](#replication) * [Script](#script) ```text $ actoolkit update -h -usage: actoolkit update [-h] {bucket,cloud,cluster,replication,script} ... +usage: actoolkit update [-h] {bucket,appVault,cloud,cluster,protection,schedule,replication,script} ... options: -h, --help show this help message and exit objectType: - {bucket,cloud,cluster,replication,script} - bucket update bucket + {bucket,appVault,cloud,cluster,protection,schedule,replication,script} + bucket (appVault) update bucket cloud update cloud cluster update cluster + protection (schedule) + update protection policy replication update replication script update script ``` @@ -147,6 +150,52 @@ $ actoolkit update cluster d0e0767b-1d77-478d-8640-13272efe1e23 --defaultBucketI {"type": "application/astra-managedCluster", "version": "1.6", "id": "d0e0767b-1d77-478d-8640-13272efe1e23", "name": "uscentral1", "state": "running", "stateUnready": [], "managedState": "managed", "protectionState": "full", "protectionStateDetails": [], "restoreTargetSupported": "true", "snapshotSupported": "true", "managedStateUnready": [], "managedTimestamp": "2023-11-28T14:30:42Z", "inUse": "true", "clusterType": "gke", "clusterVersion": "1.27", "clusterVersionString": "v1.27.3-gke.100", "connectorCapabilities": ["relayV1", "watcherV1", "neptuneV1"], "namespaces": [], "defaultStorageClass": "274562c4-9fff-4051-b16b-9db6db60651b", "cloudID": "d1c502e6-d410-46fc-8c15-f67c5b63dea2", "credentialID": "dcf1c466-d0fe-4bdf-b3ba-6e0e6e0ae066", "isMultizonal": "false", "tridentManagedStateAllowed": ["unmanaged"], "tridentVersion": "23.10.0-test.6d2477dfad063cd2277395663d5b06d198365c9e+6d2477dfad063cd2277395663d5b06d198365c9e", "acpVersion": "23.10.0-test.6d2477dfad063cd2277395663d5b06d198365c9e+3670363ff598b0105b8b2735323d3c0ae3ccabb8", "privateRouteID": "b68aa04d-a787-483f-9d9e-7c2930981534", "apiServiceID": "727422d9-abca-45d6-876e-3a6c62ef5664", "defaultBucketID": "", "metadata": {"labels": [{"name": "astra.netapp.io/labels/read-only/cloudName", "value": "private"}], "creationTimestamp": "2023-11-28T14:30:42Z", "modificationTimestamp": "2023-11-28T15:41:36Z", "createdBy": "45347ae2-6a07-41b0-a544-674ac4317b87"}} ``` +## Protection + +The `update protection` command allows you to update a [protection policy](../create/README.md#protection). The high level command usage is: + +```text +actoolkit update protection +``` + +The \ argument can be gathered from a [list protections](../list/README.md#protections) command. The available \ values are (multiple can be specified with the same command): + +* `-u`/`--bucketID`: modify the bucket where the backups and snapshots are storagegrid +* `-b`/`--backupRetention`: modify the number of backups to retain +* `-s`/`--snapshotRetention`: modify the number of snapshots to retain +* `-M`/`--dayOfMonth`: modify the day of the month for the policy (only valid for monthly granularity) +* `-W`/`--dayOfWeek`: modify the day of the week for the policy (only valid for weekly granularity) +* `-H`/`--hour`: modify the hour of the policy (valid for all granularities except hourly) +* `-m`/`--minute`: modify the minute of the policy (valid for all granularities) + +To update the bucket, run the following command: + +```text +$ actoolkit update protection 113a3140-e95d-42c9-be74-8cd169a65ae4 -u cdd3910c-6e37-4b91-beb2-57c6ed7dc7f3 +{"type": "application/astra-schedule", "version": "1.3", "id": "113a3140-e95d-42c9-be74-8cd169a65ae4", "granularity": "hourly", "minute": "5", "snapshotRetention": "1", "backupRetention": "1", "bucketID": "cdd3910c-6e37-4b91-beb2-57c6ed7dc7f3"} +``` + +To modify the number of backups retained: + +```text +$ actoolkit update protection 8cfbd961-04b9-4f7f-a094-0542aaee8626 -b 3 +{"type": "application/astra-schedule", "version": "1.3", "id": "8cfbd961-04b9-4f7f-a094-0542aaee8626", "granularity": "daily", "minute": "0", "hour": "2", "snapshotRetention": "1", "backupRetention": "3", "bucketID": "5f34da97-6195-4568-af77-52e01f9ae4bf"} +``` + +To modify the minute the policy is executed on: + +```text +$ actoolkit update protection 6967981a-cfe7-4b00-b38c-640f33223a48 -m 5 +{"type": "application/astra-schedule", "version": "1.3", "id": "6967981a-cfe7-4b00-b38c-640f33223a48", "granularity": "weekly", "minute": "5", "hour": "2", "dayOfWeek": "0", "snapshotRetention": "1", "backupRetention": "1", "bucketID": "5f34da97-6195-4568-af77-52e01f9ae4bf"} +``` + +To modify the snapshot retention, hour, and minute of a policy: + +```text +$ actoolkit update protection a3f3d02a-6c22-4d62-9436-f678d271fdc5 -m 9 -H 6 -s 3 +{"type": "application/astra-schedule", "version": "1.3", "id": "a3f3d02a-6c22-4d62-9436-f678d271fdc5", "granularity": "monthly", "minute": "9", "hour": "6", "dayOfMonth": "2", "snapshotRetention": "3", "backupRetention": "1", "bucketID": "5f34da97-6195-4568-af77-52e01f9ae4bf"} +``` + ## Replication The `update replication` command allows you to **failover**, **reverse**, or **resync** an existing [replication policy](../create/README.md#replication). It is currently **only** supported for ACC environments. The high level command usage is: diff --git a/tkSrc/choices.py b/tkSrc/choices.py index d1cf6ba..59b9c4e 100644 --- a/tkSrc/choices.py +++ b/tkSrc/choices.py @@ -191,6 +191,9 @@ def main(argv, verbs, verbPosition, ard, acl, v3, v3_skip_tls_verify=False): config_context=v3, skip_tls_verify=v3_skip_tls_verify ).main("appvaults") acl.buckets = ard.buildList("buckets", "metadata.name") + else: + ard.buckets = astraSDK.buckets.getBuckets().main() + acl.buckets = ard.buildList("buckets", "id") if argv[verbPosition + 1] == "replication": ard.destClusters = astraSDK.clusters.getClusters().main(hideUnmanaged=True) acl.destClusters = ard.buildList("destClusters", "id") @@ -470,7 +473,7 @@ def main(argv, verbs, verbPosition, ard, acl, v3, v3_skip_tls_verify=False): acl.clouds = ard.buildList("clouds", "id") elif verbs["update"] and len(argv) - verbPosition >= 2: - if argv[verbPosition + 1] == "bucket": + if argv[verbPosition + 1] == "bucket" or argv[verbPosition + 1] == "appVault": ard.buckets = astraSDK.buckets.getBuckets().main() acl.buckets = ard.buildList("buckets", "id") ard.credentials = astraSDK.credentials.getCredentials().main() @@ -513,6 +516,11 @@ def main(argv, verbs, verbPosition, ard, acl, v3, v3_skip_tls_verify=False): acl.clusters = ard.buildList("clusters", "id", fKey="managedState", fVal="managed") else: acl.clusters = ard.buildList("clusters", "id") + elif argv[verbPosition + 1] == "protection": + ard.protections = astraSDK.protections.getProtectionpolicies().main() + acl.protections = ard.buildList("protections", "id") + ard.buckets = astraSDK.buckets.getBuckets().main() + acl.buckets = ard.buildList("buckets", "id") elif argv[verbPosition + 1] == "replication": ard.replications = astraSDK.replications.getReplicationpolicies().main() if not ard.replications: # Gracefully handle ACS env diff --git a/tkSrc/create.py b/tkSrc/create.py index c740611..a9fa0f2 100644 --- a/tkSrc/create.py +++ b/tkSrc/create.py @@ -588,6 +588,14 @@ def main(args, parser, ard): parser.error("'monthly' granularity requires -M / --dayOfMonth") args.dayOfWeek = naStr if args.v3: + if ard.needsattr("buckets"): + ard.buckets = astraSDK.k8s.getResources( + config_context=args.v3, skip_tls_verify=args.skip_tls_verify + ).main("appvaults") + if args.bucket is None: + args.bucket = ard.getSingleDict("buckets", "status.state", "available", parser)[ + "metadata" + ]["name"] createV3Protection( args.v3, args.dry_run, @@ -616,6 +624,7 @@ def main(args, parser, ard): str(args.hour), str(args.minute), args.app, + bucketID=args.bucket, ) if rc is False: raise SystemExit("astraSDK.protections.createProtectionpolicy() failed") diff --git a/tkSrc/parser.py b/tkSrc/parser.py index a4ed055..79a1db2 100644 --- a/tkSrc/parser.py +++ b/tkSrc/parser.py @@ -446,6 +446,7 @@ def sub_update_commands(self): """update 'X'""" self.subparserUpdateBucket = self.subparserUpdate.add_parser( "bucket", + aliases=["appVault"], help="update bucket", ) self.subparserUpdateCloud = self.subparserUpdate.add_parser( @@ -456,6 +457,11 @@ def sub_update_commands(self): "cluster", help="update cluster", ) + self.subparserUpdateProtection = self.subparserUpdate.add_parser( + "protection", + aliases=["schedule"], + help="update protection policy", + ) self.subparserUpdateReplication = self.subparserUpdate.add_parser( "replication", help="update replication", @@ -1366,16 +1372,14 @@ def create_protection_args(self): choices=(None if self.plaidMode else self.acl.apps), help="the application to create protection schedule for", ) - if self.v3: - self.subparserCreateProtection.add_argument( - "-u", - "--appVault", - dest="bucket", - default=None, - required=True, - choices=(None if self.plaidMode else self.acl.buckets), - help="Name of the AppVault to use as the target of the backup/snapshot", - ) + self.subparserCreateProtection.add_argument( + "-u", + "--appVault" if self.v3 else "--bucket", + dest="bucket", + default=None, + choices=(None if self.plaidMode else self.acl.buckets), + help="Name of the AppVault to use as the target of the backup/snapshot", + ) self.subparserCreateProtection.add_argument( "-g", "--granularity", @@ -2020,6 +2024,52 @@ def update_cluster_args(self): help="the new default bucket / appVault for the cluster", ) + def update_protection_args(self): + """update protection args and flags""" + self.subparserUpdateProtection.add_argument( + "protection", + choices=(None if self.plaidMode else self.acl.protections), + help="protection to update", + ) + self.subparserUpdateProtection.add_argument( + "-u", + "--bucketID", + dest="bucket", + default=None, + choices=(None if self.plaidMode else self.acl.buckets), + help="the bucket to use as the target of the backup/snapshot", + ) + self.subparserUpdateProtection.add_argument( + "-b", + "--backupRetention", + type=int, + choices=range(60), + help="Number of backups to retain", + ) + self.subparserUpdateProtection.add_argument( + "-s", + "--snapshotRetention", + type=int, + choices=range(60), + help="Number of snapshots to retain", + ) + self.subparserUpdateProtection.add_argument( + "-M", "--dayOfMonth", type=int, choices=range(1, 32), help="Day of the month" + ) + self.subparserUpdateProtection.add_argument( + "-W", + "--dayOfWeek", + type=int, + choices=range(7), + help="0 = Sunday ... 6 = Saturday", + ) + self.subparserUpdateProtection.add_argument( + "-H", "--hour", type=int, choices=range(24), help="Hour in military time" + ) + self.subparserUpdateProtection.add_argument( + "-m", "--minute", default=0, type=int, choices=range(60), help="Minute" + ) + def update_replication_args(self): """update replication args and flags""" self.subparserUpdateReplication.add_argument( @@ -2137,8 +2187,9 @@ def main(self): self.update_bucket_args() self.update_cloud_args() + self.update_cluster_args() + self.update_protection_args() self.update_replication_args() self.update_script_args() - self.update_cluster_args() return self.parser diff --git a/tkSrc/update.py b/tkSrc/update.py index 430fcd9..a5d6340 100644 --- a/tkSrc/update.py +++ b/tkSrc/update.py @@ -123,6 +123,39 @@ def main(args, parser, ard): ) if rc is False: raise SystemExit("astraSDK.clusters.updateCluster() failed") + elif args.objectType == "protection" or args.objectType == "schedule": + if ard.needsattr("protections"): + ard.protections = astraSDK.protections.getProtectionpolicies().main() + protection = ard.getSingleDict("protections", "id", args.protection, parser) + granularity = protection["granularity"] + if granularity == "hourly" and args.hour: + parser.error(f"{granularity} granularity must not specify -H / --hour") + if granularity == "hourly" or granularity == "daily" or granularity == "monthly": + if args.dayOfWeek: + parser.error(f"{granularity} granularity must not specify -W / --dayOfWeek") + if granularity == "hourly" or granularity == "daily" or granularity == "weekly": + if args.dayOfMonth: + parser.error(f"{granularity} granularity must not specify -M / --dayOfMonth") + rc = astraSDK.protections.updateProtectionpolicy( + quiet=args.quiet, verbose=args.verbose + ).main( + protection["appID"], + protection["id"], + protection["granularity"], + str(args.backupRetention) if args.backupRetention else protection["backupRetention"], + ( + str(args.snapshotRetention) + if args.snapshotRetention + else protection["snapshotRetention"] + ), + minute=str(args.minute) if args.minute else protection.get("minute"), + hour=str(args.hour) if args.hour else protection.get("hour"), + dayOfWeek=str(args.dayOfWeek) if args.dayOfWeek else protection.get("dayOfWeek"), + dayOfMonth=str(args.dayOfMonth) if args.dayOfMonth else protection.get("dayOfMonth"), + bucketID=args.bucket if args.bucket else protection.get("bucketID"), + ) + if rc is False: + raise SystemExit("astraSDK.protection.updateProtectionpolicy() failed") elif args.objectType == "replication": # Gather replication data if ard.needsattr("replications"):