Skip to main content

Overview

Google Cloud Storage provides unified object storage for developers and enterprises. Multi-Cloud Manager offers APIs to manage buckets and objects across your GCP projects.

Bucket Operations

List Buckets

Retrieve all storage buckets across your GCP projects. Endpoint: GET /api/gcp/storage/buckets Authentication: Requires GCP account with valid refresh token in session Response:
{
  "value": [
    {
      "projectId": "my-project-123",
      "displayName": "My Project",
      "buckets": [
        {
          "name": "my-storage-bucket",
          "location": "US",
          "storageClass": "STANDARD",
          "timeCreated": "2024-01-15T10:30:00.000Z"
        }
      ]
    }
  ]
}
Implementation (storage.py:9-55):
from google.cloud import storage
from google.auth.transport.requests import Request

def list_gcp_buckets():
    accounts = session.get("accounts", [])
    gcp_account = None
    for acc in accounts:
        if acc.get("provider") == "gcp" and acc.get("refresh_token"):
            gcp_account = acc
            break
    
    if not gcp_account:
        return jsonify({"error": "Nie znaleziono aktywnego konta GCP w sesji"}), 404
    
    if not isinstance(gcp_account.get("access_token"), str) or not gcp_account.get("refresh_token"):
        return jsonify({"error": "Brak kompletnych lub poprawnych tokenów w sesji. Proszę zalogować się ponownie."}), 401

    try:
        credentials = SessionCredentials(gcp_account)
        all_projects_with_buckets = []
        
        # Get all accessible projects
        projects = list_gcp_projects(credentials)

        for project in projects:
            current_project_buckets = []
            project_id = project.get("projectId")
            
            try:
                storage_client = storage.Client(project=project_id, credentials=credentials)
                
                # List all buckets in the project
                for bucket in storage_client.list_buckets():
                    current_project_buckets.append({
                        "name": bucket.name,
                        "location": bucket.location,
                        "storageClass": bucket.storage_class,
                        "timeCreated": bucket.time_created.isoformat() if bucket.time_created else None,
                    })

                if current_project_buckets:
                    all_projects_with_buckets.append({
                        "projectId": project_id,
                        "displayName": project.get("name"),
                        "buckets": current_project_buckets
                    })

            except Exception as e:
                print(f"Pominięto projekt {project.get('projectId')} z powodu błędu: {e}")
                continue
        
        return jsonify({"value": all_projects_with_buckets})
        
    except Exception as e:
        return jsonify({"error": f"Wystąpił ogólny błąd: {str(e)}"}), 500
The function iterates through all accessible GCP projects and lists buckets for each. Projects without proper permissions are skipped gracefully.

Create Bucket

Create a new GCP storage bucket with custom configuration. Endpoint: POST /api/gcp/storage/buckets Request Body:
{
  "bucketName": "my-unique-bucket-name",
  "projectId": "my-project-123",
  "location": "US",
  "storageClass": "STANDARD"
}
Available Storage Classes:
  • STANDARD - Best for frequently accessed data
  • NEARLINE - Low-cost for data accessed less than once a month
  • COLDLINE - Very low-cost for data accessed less than once a quarter
  • ARCHIVE - Lowest-cost for data accessed less than once a year
Implementation (storage.py:98-142):
from google.cloud.exceptions import Conflict, Forbidden

def create_gcp_bucket():
    accounts = session.get("accounts", [])
    gcp_account = next((acc for acc in accounts if acc.get("provider") == "gcp"), None)

    if not gcp_account:
        return jsonify({"error": "Nie znaleziono aktywnego konta GCP w sesji"}), 401
    
    if not gcp_account.get("refresh_token"):
        return jsonify({"error": "Brak kompletnych tokenów w sesji. Proszę zalogować się ponownie."}), 401

    data = request.get_json()
    if not data:
        return jsonify({"error": "Brak danych w ciele żądania."}), 400
        
    bucket_name = data.get("bucketName")
    project_id = data.get("projectId")
    storage_class = data.get("storageClass", "STANDARD")
    location = data.get("location")

    if not all([bucket_name, project_id, location]):
        return jsonify({"error": "Pola 'bucketName', 'projectId' oraz 'location' są wymagane."}), 400

    try:
        credentials = SessionCredentials(gcp_account)
        credentials.refresh(Request())

        storage_client = storage.Client(project=project_id, credentials=credentials)

        bucket = storage_client.bucket(bucket_name)
        bucket.storage_class = storage_class
        
        # Enable uniform bucket-level access for better security
        bucket.iam_configuration.uniform_bucket_level_access_enabled = True

        new_bucket = storage_client.create_bucket(bucket, location=location)
        
        return jsonify({
            "message": f"Bucket '{new_bucket.name}' został pomyślnie utworzony w lokalizacji '{new_bucket.location}'."
        }), 201

    except Conflict:
        return jsonify({"error": f"Nazwa bucketa '{bucket_name}' jest już zajęta. Nazwy bucketów muszą być unikalne globalnie."}), 409
    except Forbidden as e:
        return jsonify({"error": f"Brak uprawnień do tworzenia bucketa w projekcie '{project_id}'. Sprawdź uprawnienia IAM. Szczegóły: {e}"}), 403
    except Exception as e:
        return jsonify({"error": f"Wystąpił nieoczekiwany błąd serwera: {str(e)}"}), 500
Bucket names must be globally unique across all of GCP. Use DNS-compliant naming (lowercase letters, numbers, hyphens).

Delete Bucket

Delete a storage bucket, optionally with all its contents. Endpoint: DELETE /api/gcp/storage/buckets Request Body:
{
  "bucketName": "my-bucket",
  "projectId": "my-project-123",
  "force": true
}
Parameters:
  • force (boolean): If true, deletes all blobs before deleting the bucket
Implementation (storage.py:57-96):
from google.cloud.exceptions import Conflict, Forbidden, NotFound

def delete_gcp_bucket():
    accounts = session.get("accounts", [])
    gcp_account = next((acc for acc in accounts if acc.get("provider") == "gcp"), None)

    if not gcp_account:
        return jsonify({"error": "Nie znaleziono aktywnego konta GCP w sesji"}), 401
    
    if not gcp_account.get("refresh_token"):
        return jsonify({"error": "Brak kompletnych tokenów w sesji. Proszę zalogować się ponownie."}), 401

    data = request.get_json()
    if not data:
        return jsonify({"error": "Brak danych w ciele żądania."}), 400
        
    bucket_name = data.get("bucketName")
    force_delete = data.get("force", False)
    project_id = data.get("projectId")

    if not bucket_name:
        return jsonify({"error": "Nazwa bucketa ('bucketName') jest wymagana."}), 400

    try:
        credentials = SessionCredentials(gcp_account)
        credentials.refresh(Request())
        storage_client = storage.Client(project=project_id, credentials=credentials)
        bucket = storage_client.get_bucket(bucket_name)

        # Force delete: remove all blobs first
        if force_delete:
            blobs = list(bucket.list_blobs())
            bucket.delete_blobs(blobs)
        
        bucket.delete()
        
        return jsonify({"message": f"Bucket '{bucket_name}' został pomyślnie usunięty."}), 200

    except Conflict:
        return jsonify({"error": f"Bucket '{bucket_name}' nie jest pusty. Opróżnij go lub użyj opcji usuwania z zawartością."}), 409
    except Forbidden:
        return jsonify({"error": f"Brak uprawnień do usunięcia bucketa '{bucket_name}'. Sprawdź, czy logowałeś się z pełnymi uprawnieniami (nie read-only)."}), 403
    except Exception as e:
        return jsonify({"error": f"Wystąpił nieoczekiwany błąd serwera: {str(e)}"}), 500
Without force: true, attempting to delete a non-empty bucket will return a 409 Conflict error.

Object Operations

List Blobs

List all objects in a bucket. Endpoint: GET /api/gcp/storage/blobs Query Parameters:
  • bucketName - Name of the bucket
  • projectId - GCP project ID
Response:
{
  "value": [
    {
      "name": "documents/report.pdf",
      "size": 1048576,
      "updated": "2024-03-05T14:30:00.000Z"
    }
  ]
}
Implementation (storage.py:145-177):
def list_bucket_blobs():
    accounts = session.get("accounts", [])
    gcp_account = next((acc for acc in accounts if acc.get("provider") == "gcp"), None)

    if not gcp_account:
        return jsonify({"error": "Nie znaleziono aktywnego konta GCP w sesji"}), 401
    
    if not gcp_account.get("refresh_token"):
        return jsonify({"error": "Brak kompletnych tokenów w sesji. Proszę zalogować się ponownie."}), 401
        
    bucket_name = request.args.get("bucketName")
    project_id = request.args.get("projectId")

    try:
        credentials = SessionCredentials(gcp_account)
        storage_client = storage.Client(project=project_id, credentials=credentials)
        bucket = storage_client.get_bucket(bucket_name)
        
        blobs = list(bucket.list_blobs())
        
        result = [{
            "name": b.name,
            "size": b.size,
            "updated": b.updated.isoformat()
        } for b in blobs]
        
        return jsonify({"value": result})

    except NotFound:
        return jsonify({"error": f"Bucket o nazwie '{bucket_name}' nie istnieje."}), 404
    except Forbidden:
        return jsonify({"error": f"Brak uprawnień do listowania obiektów w buckecie '{bucket_name}'."}), 403
    except Exception as e:
        return jsonify({"error": f"Wystąpił nieoczekiwany błąd serwera: {str(e)}"}), 500

Error Handling

The GCP storage API provides detailed error responses:
  • 404 Not Found: Bucket or object doesn’t exist
  • 403 Forbidden: Insufficient IAM permissions
  • 409 Conflict: Bucket name already taken or bucket not empty
  • 401 Unauthorized: Missing or invalid credentials

Next Steps

Blob Management

Learn how to upload, download, and delete objects

Build docs developers (and LLMs) love