2020import com .google .cloud .AuthCredentials .ServiceAccountAuthCredentials ;
2121import com .google .cloud .ReadChannel ;
2222import com .google .cloud .WriteChannel ;
23+ import com .google .cloud .storage .Acl ;
2324import com .google .cloud .storage .Blob ;
2425import com .google .cloud .storage .BlobId ;
2526import com .google .cloud .storage .BlobInfo ;
3031import com .google .cloud .storage .Storage .CopyRequest ;
3132import com .google .cloud .storage .Storage .SignUrlOption ;
3233import com .google .cloud .storage .StorageOptions ;
34+ import com .google .cloud .storage .spi .StorageRpc ;
3335import com .google .cloud .storage .spi .StorageRpc .Tuple ;
36+ import com .google .common .collect .ImmutableMap ;
3437
3538import java .io .FileOutputStream ;
3639import java .io .IOException ;
5154import java .util .Arrays ;
5255import java .util .HashMap ;
5356import java .util .Iterator ;
57+ import java .util .LinkedList ;
5458import java .util .List ;
5559import java .util .Map ;
5660import java .util .concurrent .TimeUnit ;
7579 * cp <from_bucket> <from_path> <to_bucket> <to_path> |
7680 * compose <bucket> <from_path>+ <to_path> |
7781 * update_metadata <bucket> <file> [key=value]* |
78- * sign_url <service_account_private_key_file> <service_account_email> <bucket> <path>"}</pre>
82+ * sign_url <service_account_private_key_file> <service_account_email> <bucket> <path> |
83+ * add-acl domain <bucket> <path>? <domain> OWNER|READER|WRITER |
84+ * add-acl project <bucket> <path>? <projectId>:(OWNERS|EDITORS|VIEWERS) OWNER|READER|WRITER |
85+ * add-acl user <bucket> <path>? <userEmail>|allUsers|allAuthenticatedUsers OWNER|READER|WRITER |
86+ * add-acl group <bucket> <path>? <group> OWNER|READER|WRITER"}</pre>
7987 * </li>
8088 * </ol>
8189 *
8795public class StorageExample {
8896
8997 private static final Map <String , StorageAction > ACTIONS = new HashMap <>();
98+ private static final Map <String , StorageAction > ACL_ACTIONS = new HashMap <>();
9099
91100 private abstract static class StorageAction <T > {
92101
@@ -119,6 +128,48 @@ public String params() {
119128 }
120129 }
121130
131+ private static class ParentAction extends StorageAction <StorageRpc .Tuple <StorageAction , Object >> {
132+
133+ private final Map <String , StorageAction > subActions ;
134+
135+ ParentAction (Map <String , StorageAction > subActions ) {
136+ this .subActions = ImmutableMap .copyOf (subActions );
137+ }
138+
139+ @ Override
140+ @ SuppressWarnings ("unchecked" )
141+ void run (Storage storage , StorageRpc .Tuple <StorageAction , Object > subaction ) throws Exception {
142+ subaction .x ().run (storage , subaction .y ());
143+ }
144+
145+ @ Override
146+ StorageRpc .Tuple <StorageAction , Object > parse (String ... args ) throws Exception {
147+ if (args .length >= 1 ) {
148+ StorageAction action = subActions .get (args [0 ]);
149+ if (action != null ) {
150+ Object actionArguments = action .parse (Arrays .copyOfRange (args , 1 , args .length ));
151+ return StorageRpc .Tuple .of (action , actionArguments );
152+ } else {
153+ throw new IllegalArgumentException ("Unrecognized entity '" + args [0 ] + "'." );
154+ }
155+ }
156+ throw new IllegalArgumentException ("Missing required entity." );
157+ }
158+
159+ @ Override
160+ public String params () {
161+ StringBuilder builder = new StringBuilder ();
162+ for (Map .Entry <String , StorageAction > entry : subActions .entrySet ()) {
163+ builder .append ('\n' ).append (entry .getKey ());
164+ String param = entry .getValue ().params ();
165+ if (param != null && !param .isEmpty ()) {
166+ builder .append (' ' ).append (param );
167+ }
168+ }
169+ return builder .toString ();
170+ }
171+ }
172+
122173 /**
123174 * This class demonstrates how to retrieve Bucket or Blob metadata.
124175 * If more than one blob is supplied a Batch operation would be used to get all blobs metadata
@@ -127,6 +178,12 @@ public String params() {
127178 * @see <a href="https://cloud.google.com/storage/docs/json_api/v1/objects/get">Objects: get</a>
128179 */
129180 private static class InfoAction extends BlobsAction {
181+
182+ /**
183+ * Gets information for the provided blobs, using the {@code storage} service. If
184+ * {@code blobIds} contains only one blob identity and {@code blobIds[0].name()} is empty, this
185+ * method gets information for the bucket identified by {@code blobIds[0].bucket()}.
186+ */
130187 @ Override
131188 public void run (Storage storage , BlobId ... blobIds ) {
132189 if (blobIds .length == 1 ) {
@@ -512,6 +569,194 @@ public String params() {
512569 }
513570 }
514571
572+ private abstract static class AclAction extends StorageAction <Tuple <BlobId , Acl >> {
573+
574+ /**
575+ * Sets the ACL according to the provided {@code params}, using the {@code storage} service. If
576+ * {@code params.x()} returns a complete blob identity, the {@code params.y()} ACL is added to
577+ * the blob. If {@code params.x().name()} is empty, the {@code params.y()} ACL is added to the
578+ * bucket identified by {@code params.x().bucket()}.
579+ */
580+ @ Override
581+ public void run (Storage storage , Tuple <BlobId , Acl > params ) {
582+ BlobId blobId = params .x ();
583+ Acl acl = params .y ();
584+ if (blobId .name ().isEmpty ()) {
585+ Bucket bucket = storage .get (blobId .bucket ());
586+ if (bucket == null ) {
587+ System .out .printf ("Bucket %s does not exist%n" , blobId .bucket ());
588+ return ;
589+ }
590+ bucket .toBuilder ().acl (addAcl (bucket .acl (), acl )).build ().update ();
591+ System .out .printf ("Added ACL %s to bucket %s%n" , acl , blobId .bucket ());
592+ } else {
593+ Blob blob = storage .get (blobId );
594+ if (blob == null ) {
595+ System .out .printf ("Blob %s does not exist%n" , blobId );
596+ return ;
597+ }
598+ blob .toBuilder ().acl (addAcl (blob .acl (), acl )).build ().update ();
599+ System .out .printf ("Added ACL %s to blob %s%n" , acl , blobId );
600+ }
601+ }
602+
603+ private static List <Acl > addAcl (List <Acl > acls , Acl newAcl ) {
604+ List <Acl > newAcls = new LinkedList <>(acls );
605+ newAcls .add (newAcl );
606+ return newAcls ;
607+ }
608+ }
609+
610+ /**
611+ * This class demonstrates how to add an ACL to a blob or a bucket for a group of users
612+ * (identified by the group's email).
613+ *
614+ * @see <a href="https://cloud.google.com/storage/docs/access-control/lists#permissions">Access
615+ * Control Lists (ACLs)</a>
616+ */
617+ private static class AddGroupAclAction extends AclAction {
618+
619+ @ Override
620+ Tuple <BlobId , Acl > parse (String ... args ) {
621+ if (args .length >= 3 ) {
622+ BlobId blob ;
623+ int nextArg ;
624+ if (args .length == 3 ) {
625+ blob = BlobId .of (args [0 ], "" );
626+ nextArg = 1 ;
627+ } else if (args .length == 4 ) {
628+ blob = BlobId .of (args [0 ], args [1 ]);
629+ nextArg = 2 ;
630+ } else {
631+ throw new IllegalArgumentException ("Too many arguments." );
632+ }
633+ String group = args [nextArg ++];
634+ Acl .Role role = Acl .Role .valueOf (args [nextArg ]);
635+ return Tuple .of (blob , Acl .of (new Acl .Group (group ), role ));
636+ }
637+ throw new IllegalArgumentException ("Missing required bucket, groupEmail or role arguments." );
638+ }
639+
640+ @ Override
641+ public String params () {
642+ return "<bucket> <path>? <group> OWNER|READER|WRITER" ;
643+ }
644+ }
645+
646+ /**
647+ * This class demonstrates how to add an ACL to a blob or a bucket for a domain.
648+ *
649+ * @see <a href="https://cloud.google.com/storage/docs/access-control/lists#permissions">Access
650+ * Control Lists (ACLs)</a>
651+ */
652+ private static class AddDomainAclAction extends AclAction {
653+
654+ @ Override
655+ Tuple <BlobId , Acl > parse (String ... args ) {
656+ if (args .length >= 3 ) {
657+ BlobId blob ;
658+ int nextArg ;
659+ if (args .length == 3 ) {
660+ blob = BlobId .of (args [0 ], "" );
661+ nextArg = 1 ;
662+ } else if (args .length == 4 ) {
663+ blob = BlobId .of (args [0 ], args [1 ]);
664+ nextArg = 2 ;
665+ } else {
666+ throw new IllegalArgumentException ("Too many arguments." );
667+ }
668+ String domain = args [nextArg ++];
669+ Acl .Role role = Acl .Role .valueOf (args [nextArg ]);
670+ return Tuple .of (blob , Acl .of (new Acl .Domain (domain ), role ));
671+ }
672+ throw new IllegalArgumentException ("Missing required bucket, domain or role arguments." );
673+ }
674+
675+ @ Override
676+ public String params () {
677+ return "<bucket> <path>? <domain> OWNER|READER|WRITER" ;
678+ }
679+ }
680+
681+ /**
682+ * This class demonstrates how to add an ACL to a blob or a bucket for either a user (if an email
683+ * is provided), all users (if {@code allUsers} is provided), or all authenticated users (if
684+ * {@code allAuthenticatedUsers} is provided).
685+ *
686+ * @see <a href="https://cloud.google.com/storage/docs/access-control/lists#permissions">Access
687+ * Control Lists (ACLs)</a>
688+ */
689+ private static class AddUserAclAction extends AclAction {
690+
691+ @ Override
692+ Tuple <BlobId , Acl > parse (String ... args ) {
693+ if (args .length >= 3 ) {
694+ BlobId blob ;
695+ int nextArg ;
696+ if (args .length == 3 ) {
697+ blob = BlobId .of (args [0 ], "" );
698+ nextArg = 1 ;
699+ } else if (args .length == 4 ) {
700+ blob = BlobId .of (args [0 ], args [1 ]);
701+ nextArg = 2 ;
702+ } else {
703+ throw new IllegalArgumentException ("Too many arguments." );
704+ }
705+ String user = args [nextArg ++];
706+ Acl .Role role = Acl .Role .valueOf (args [nextArg ]);
707+ return Tuple .of (blob , Acl .of (new Acl .User (user ), role ));
708+ }
709+ throw new IllegalArgumentException ("Missing required bucket, userEmail or role arguments." );
710+ }
711+
712+ @ Override
713+ public String params () {
714+ return "<bucket> <path>? <userEmail>|allUsers|allAuthenticatedUsers OWNER|READER|WRITER" ;
715+ }
716+ }
717+
718+ /**
719+ * This class demonstrates how to add an ACL to a blob or a bucket for all users that have a
720+ * specific role in a provided project.
721+ *
722+ * @see <a href="https://cloud.google.com/storage/docs/access-control/lists#permissions">Access
723+ * Control Lists (ACLs)</a>
724+ */
725+ private static class AddProjectAclAction extends AclAction {
726+
727+ @ Override
728+ Tuple <BlobId , Acl > parse (String ... args ) {
729+ if (args .length >= 3 ) {
730+ BlobId blob ;
731+ int nextArg ;
732+ if (args .length == 3 ) {
733+ blob = BlobId .of (args [0 ], "" );
734+ nextArg = 1 ;
735+ } else if (args .length == 4 ) {
736+ blob = BlobId .of (args [0 ], args [1 ]);
737+ nextArg = 2 ;
738+ } else {
739+ throw new IllegalArgumentException ("Too many arguments." );
740+ }
741+ String [] projectAndRole = args [nextArg ++].split (":" );
742+ if (projectAndRole .length != 2 ) {
743+ throw new IllegalArgumentException (
744+ "Project entity must be specified as <projectId>:(OWNERS|READERS|WRITERS)" );
745+ } else {
746+ Acl .Project .ProjectRole projectRole = Acl .Project .ProjectRole .valueOf (projectAndRole [1 ]);
747+ Acl .Role role = Acl .Role .valueOf (args [nextArg ]);
748+ return Tuple .of (blob , Acl .of (new Acl .Project (projectRole , projectAndRole [0 ]), role ));
749+ }
750+ }
751+ throw new IllegalArgumentException ("Missing required bucket, project or role arguments." );
752+ }
753+
754+ @ Override
755+ public String params () {
756+ return "<bucket> <path>? <projectId>:(OWNERS|EDITORS|VIEWERS) OWNER|READER|WRITER" ;
757+ }
758+ }
759+
515760 static {
516761 ACTIONS .put ("info" , new InfoAction ());
517762 ACTIONS .put ("delete" , new DeleteAction ());
@@ -522,6 +767,11 @@ public String params() {
522767 ACTIONS .put ("compose" , new ComposeAction ());
523768 ACTIONS .put ("update_metadata" , new UpdateMetadataAction ());
524769 ACTIONS .put ("sign_url" , new SignUrlAction ());
770+ ACL_ACTIONS .put ("group" , new AddGroupAclAction ());
771+ ACL_ACTIONS .put ("domain" , new AddDomainAclAction ());
772+ ACL_ACTIONS .put ("user" , new AddUserAclAction ());
773+ ACL_ACTIONS .put ("project" , new AddProjectAclAction ());
774+ ACTIONS .put ("add-acl" , new ParentAction (ACL_ACTIONS ));
525775 }
526776
527777 private static void printUsage () {
@@ -531,10 +781,11 @@ private static void printUsage() {
531781
532782 String param = entry .getValue ().params ();
533783 if (param != null && !param .isEmpty ()) {
534- actionAndParams .append (' ' ).append (param );
784+ // Add extra padding for multi-line action
785+ actionAndParams .append (' ' ).append (param .replace ("\n " , "\n \t \t " ));
535786 }
536787 }
537- System .out .printf ("Usage: %s [<project_id>] operation <args>*%s%n" ,
788+ System .out .printf ("Usage: %s [<project_id>] operation [entity] <args>*%s%n" ,
538789 StorageExample .class .getSimpleName (), actionAndParams );
539790 }
540791
0 commit comments