|
| 1 | +// Package cli provides easy-to-use commands to manage, monitor, and utilize AIS clusters. |
| 2 | +// This file handles commands that interact with the cluster. |
| 3 | +/* |
| 4 | + * Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. |
| 5 | + */ |
| 6 | +package cli |
| 7 | + |
| 8 | +import ( |
| 9 | + "fmt" |
| 10 | + "sort" |
| 11 | + |
| 12 | + "github.com/NVIDIA/aistore/api" |
| 13 | + "github.com/NVIDIA/aistore/api/apc" |
| 14 | + "github.com/NVIDIA/aistore/cmd/cli/teb" |
| 15 | + "github.com/NVIDIA/aistore/cmn/cos" |
| 16 | + |
| 17 | + "github.com/urfave/cli" |
| 18 | +) |
| 19 | + |
| 20 | +const ( |
| 21 | + createNBIUsage = "Create bucket inventory (snapshot) for subsequent fast listing,\n" + |
| 22 | + indent1 + "e.g.:\n" + |
| 23 | + indent1 + "\t* ais nbi create s3://abc\t- create inventory with default (name, size) properties;\n" + |
| 24 | + indent1 + "\t* ais nbi create s3://abc --inv-name my-first-inventory\t- same, with a custom inventory name;\n" + |
| 25 | + indent1 + "\t* ais nbi create s3://abc --prefix images/\t- inventory only objects under 'images/';\n" + |
| 26 | + indent1 + "\t* ais nbi create s3://abc --all\t- inventory with all object properties;\n" + |
| 27 | + indent1 + "\t* ais nbi create s3://abc --name-only\t- lightweight: object names only;\n" + |
| 28 | + indent1 + "\t* ais nbi create ais://@remais/xyz --inv-pages 2\t- remote AIS, with 2 pages per chunk." |
| 29 | + |
| 30 | + removeNBIUsage = "Remove bucket inventory,\n" + |
| 31 | + indent1 + "e.g.:\n" + |
| 32 | + indent1 + "\t* ais nbi rm s3://abc\t- remove inventory for the bucket;\n" + |
| 33 | + indent1 + "\t* ais nbi rm s3://abc --inv-name my-first-inventory\t- remove specific named inventory." |
| 34 | + |
| 35 | + showNBIUsage = "Show bucket inventory,\n" + |
| 36 | + indent1 + "e.g.:\n" + |
| 37 | + indent1 + "\t* ais nbi show s3://abc\t- show inventory details for the bucket;\n" + |
| 38 | + indent1 + "\t* ais nbi show s3://abc --inv-name my-first-inventory\t- show specific named inventory." |
| 39 | +) |
| 40 | + |
| 41 | +// flags |
| 42 | +var ( |
| 43 | + nbiCmdFlags = map[string][]cli.Flag{ |
| 44 | + commandCreate: { |
| 45 | + nbiNameFlag, |
| 46 | + invPrefixFlag, |
| 47 | + nameOnlyFlag, |
| 48 | + allPropsFlag, |
| 49 | + nbiPagesPerChunkFlag, |
| 50 | + nbiMaxEntriesPerChunkFlag, |
| 51 | + nbiForceFlag, |
| 52 | + }, |
| 53 | + commandRemove: { |
| 54 | + nbiNameFlag, |
| 55 | + }, |
| 56 | + commandShow: { |
| 57 | + nbiNameFlag, |
| 58 | + verboseFlag, |
| 59 | + }, |
| 60 | + } |
| 61 | +) |
| 62 | + |
| 63 | +// commands |
| 64 | +var ( |
| 65 | + cmdCreateNBI = cli.Command{ |
| 66 | + Name: commandCreate, |
| 67 | + Usage: createNBIUsage, |
| 68 | + ArgsUsage: bucketArgument, |
| 69 | + Flags: sortFlags(nbiCmdFlags[commandCreate]), |
| 70 | + Action: createNBIHandler, |
| 71 | + BashComplete: bucketCompletions(bcmplop{}), |
| 72 | + } |
| 73 | + cmdRemoveNBI = cli.Command{ |
| 74 | + Name: commandRemove, |
| 75 | + Usage: removeNBIUsage, |
| 76 | + ArgsUsage: bucketArgument, |
| 77 | + Flags: sortFlags(nbiCmdFlags[commandRemove]), |
| 78 | + Action: removeNBIHandler, |
| 79 | + BashComplete: bucketCompletions(bcmplop{}), |
| 80 | + } |
| 81 | + cmdShowNBI = cli.Command{ |
| 82 | + Name: commandShow, |
| 83 | + Usage: showNBIUsage, |
| 84 | + ArgsUsage: bucketArgument, |
| 85 | + Flags: sortFlags(nbiCmdFlags[commandShow]), |
| 86 | + Action: showNBIHandler, |
| 87 | + BashComplete: bucketCompletions(bcmplop{}), |
| 88 | + } |
| 89 | + |
| 90 | + // top-level |
| 91 | + nbiCmd = cli.Command{ |
| 92 | + Name: commandNBI, |
| 93 | + Usage: "Manage native bucket inventory (NBI) - create (to facilitate faster list-objects), show, or remove bucket inventory snapshots", |
| 94 | + Subcommands: []cli.Command{ |
| 95 | + cmdCreateNBI, |
| 96 | + cmdRemoveNBI, |
| 97 | + cmdShowNBI, // TODO -- FIXME: add `ais show nbi` alias |
| 98 | + }, |
| 99 | + } |
| 100 | +) |
| 101 | + |
| 102 | +// |
| 103 | +// createNBIHandler |
| 104 | +// |
| 105 | + |
| 106 | +func createNBIHandler(c *cli.Context) error { |
| 107 | + if c.NArg() == 0 { |
| 108 | + return missingArgumentsError(c, c.Command.ArgsUsage) |
| 109 | + } |
| 110 | + if c.NArg() > 1 { |
| 111 | + return incorrectUsageMsg(c, "", c.Args()[1:]) |
| 112 | + } |
| 113 | + bck, err := parseBckURI(c, c.Args().Get(0), false) |
| 114 | + if err != nil { |
| 115 | + return err |
| 116 | + } |
| 117 | + |
| 118 | + // msg |
| 119 | + msg := &apc.CreateNBIMsg{} |
| 120 | + msg.SetFlag(apc.LsNoDirs) |
| 121 | + |
| 122 | + if flagIsSet(c, invPrefixFlag) { |
| 123 | + msg.Prefix = parseStrFlag(c, invPrefixFlag) |
| 124 | + } |
| 125 | + |
| 126 | + switch { |
| 127 | + case flagIsSet(c, nameOnlyFlag): |
| 128 | + msg.SetFlag(apc.LsNameOnly) |
| 129 | + msg.Props = apc.GetPropsName // abs. minimum |
| 130 | + case flagIsSet(c, allPropsFlag): |
| 131 | + msg.AddProps(apc.GetPropsAll...) // all supported props |
| 132 | + default: |
| 133 | + msg.AddProps(apc.GetPropsName, apc.GetPropsSize, apc.GetPropsCached) // NOTE default |
| 134 | + } |
| 135 | + |
| 136 | + // inv name |
| 137 | + if flagIsSet(c, nbiNameFlag) { |
| 138 | + msg.Name = parseStrFlag(c, nbiNameFlag) |
| 139 | + if err := cos.CheckAlphaPlus(msg.Name, "inventory name"); err != nil { |
| 140 | + return err |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + // advanced |
| 145 | + if flagIsSet(c, nbiPagesPerChunkFlag) { |
| 146 | + a := parseIntFlag(c, nbiPagesPerChunkFlag) |
| 147 | + msg.PagesPerChunk = int64(a) |
| 148 | + } |
| 149 | + if flagIsSet(c, nbiMaxEntriesPerChunkFlag) { |
| 150 | + a := parseIntFlag(c, nbiMaxEntriesPerChunkFlag) |
| 151 | + msg.MaxEntriesPerChunk = int64(a) |
| 152 | + } |
| 153 | + msg.Force = flagIsSet(c, nbiForceFlag) |
| 154 | + |
| 155 | + // do |
| 156 | + xid, err := api.CreateNBI(apiBP, bck, msg) |
| 157 | + if err != nil { |
| 158 | + return V(err) |
| 159 | + } |
| 160 | + |
| 161 | + actionDone(c, fmt.Sprintf("Creating inventory %s. %s", bck.Cname(""), toMonitorMsg(c, xid, ""))) |
| 162 | + return nil |
| 163 | +} |
| 164 | + |
| 165 | +// |
| 166 | +// removeNBIHandler |
| 167 | +// |
| 168 | + |
| 169 | +func removeNBIHandler(c *cli.Context) error { |
| 170 | + if c.NArg() == 0 { |
| 171 | + return missingArgumentsError(c, c.Command.ArgsUsage) |
| 172 | + } |
| 173 | + if c.NArg() > 1 { |
| 174 | + return incorrectUsageMsg(c, "", c.Args()[1:]) |
| 175 | + } |
| 176 | + |
| 177 | + // bucket |
| 178 | + bck, err := parseBckURI(c, c.Args().Get(0), false) |
| 179 | + if err != nil { |
| 180 | + return err |
| 181 | + } |
| 182 | + |
| 183 | + // inv name |
| 184 | + var ( |
| 185 | + s string |
| 186 | + invName = parseStrFlag(c, nbiNameFlag) |
| 187 | + ) |
| 188 | + if invName != "" { |
| 189 | + if err := cos.CheckAlphaPlus(invName, "inventory name"); err != nil { |
| 190 | + return err |
| 191 | + } |
| 192 | + s = " '" + invName + "'" |
| 193 | + } |
| 194 | + if err := api.DestroyNBI(apiBP, bck, invName); err != nil { |
| 195 | + return V(err) |
| 196 | + } |
| 197 | + |
| 198 | + actionDone(c, fmt.Sprintf("Removed inventory%s for bucket %s", s, bck.String())) |
| 199 | + return nil |
| 200 | +} |
| 201 | + |
| 202 | +// |
| 203 | +// showNBIHandler |
| 204 | +// |
| 205 | + |
| 206 | +func showNBIHandler(c *cli.Context) error { |
| 207 | + if c.NArg() == 0 { |
| 208 | + return missingArgumentsError(c, c.Command.ArgsUsage) |
| 209 | + } |
| 210 | + if c.NArg() > 1 { |
| 211 | + return incorrectUsageMsg(c, "", c.Args()[1:]) |
| 212 | + } |
| 213 | + |
| 214 | + bck, err := parseBckURI(c, c.Args().Get(0), false) |
| 215 | + if err != nil { |
| 216 | + return err |
| 217 | + } |
| 218 | + |
| 219 | + invName := parseStrFlag(c, nbiNameFlag) |
| 220 | + if invName != "" { |
| 221 | + if err := cos.CheckAlphaPlus(invName, "inventory name"); err != nil { |
| 222 | + return err |
| 223 | + } |
| 224 | + } |
| 225 | + |
| 226 | + infos, err := api.GetNBI(apiBP, bck, invName) |
| 227 | + if err != nil { |
| 228 | + return V(err) |
| 229 | + } |
| 230 | + |
| 231 | + type extInfo struct { |
| 232 | + apc.NBIInfo |
| 233 | + Bucket string |
| 234 | + } |
| 235 | + |
| 236 | + lst := make([]*extInfo, 0, len(infos)) |
| 237 | + for _, v := range infos { |
| 238 | + var ext extInfo |
| 239 | + ext.NBIInfo = *v |
| 240 | + ext.Bucket = bck.Cname("") |
| 241 | + lst = append(lst, &ext) |
| 242 | + } |
| 243 | + sort.Slice(lst, func(i, j int) bool { return lst[i].Name < lst[j].Name }) |
| 244 | + |
| 245 | + if flagIsSet(c, verboseFlag) { |
| 246 | + return teb.Print(lst, teb.NBITmplVerbose) |
| 247 | + } |
| 248 | + return teb.Print(lst, teb.NBITmpl) |
| 249 | +} |
0 commit comments