העלאת אובייקטים ממערכת קבצים

המאמר הזה מתאר איך להעלות אובייקטים ממערכת הקבצים המקומית לקטגוריה של Cloud Storage. אובייקט שהועלה מכיל את הנתונים שרוצים לשמור יחד עם המטא-נתונים שמשויכים אליהם. במאמר העלאות והורדות אפשר לקבל סקירה כללית של מושגים, כולל איך לבחור את שיטת ההעלאה האופטימלית בהתאם לגודל הקובץ.

לקבלת הוראות להעלאה מהזיכרון, אפשר לעיין במאמר העלאת אובייקטים מהזיכרון.

ההרשאות הנדרשות

המסוף

כדי להשלים את המדריך הזה באמצעות מסוף Google Cloud, אתם צריכים הרשאות IAM מתאימות. אם הקטגוריה שאתם רוצים לגשת אליה קיימת בפרויקט שלא אתם יצרתם, יכול להיות שתצטרכו לבקש מבעלי הפרויקט להקצות לכם תפקיד שמכיל את ההרשאות הנדרשות.

רשימת ההרשאות הנדרשות לפעולות ספציפיות מופיעה במאמר הרשאות IAM במסוף Google Cloud.

רשימת התפקידים הרלוונטיים מופיעה במאמר תפקידים ב-Cloud Storage. לחלופין, אפשר ליצור תפקיד בהתאמה אישית עם הרשאות ספציפיות ומוגבלות.

שורת הפקודה

כדי להשלים את המדריך הזה באמצעות כלי שורת הפקודה, אתם צריכים הרשאות IAM מתאימות. אם הקטגוריה שאתם רוצים לגשת אליה קיימת בפרויקט שלא אתם יצרתם, יכול להיות שתצטרכו לבקש מבעלי הפרויקט להקצות לכם תפקיד שמכיל את ההרשאות הנדרשות.

רשימת ההרשאות הנדרשות לפעולות ספציפיות מופיעה במאמר הרשאות IAM לפקודות gsutil.

רשימת התפקידים הרלוונטיים מופיעה במאמר תפקידים ב-Cloud Storage. לחלופין, אפשר ליצור תפקיד בהתאמה אישית עם הרשאות ספציפיות ומוגבלות.

ספריות לקוח

כדי להשלים את המדריך הזה באמצעות ספריות הלקוח של Cloud Storage, אתם צריכים הרשאות IAM מתאימות. אם הקטגוריה שאתם רוצים לגשת אליה קיימת בפרויקט שלא אתם יצרתם, יכול להיות שתצטרכו לבקש מבעלי הפרויקט להקצות לכם תפקיד שמכיל את ההרשאות הנדרשות.

אם לא צוין אחרת, בקשות לספריות לקוח מבוצעות דרך API בפורמט JSON ומחייבות את ההרשאות שמפורטות בהרשאות IAM ל-methods של JSON. כדי לראות אילו methods של API בפורמט JSON מופעלות כששולחים בקשות באמצעות ספריית לקוח, אתם צריכים לבצע רישום ביומן של הבקשות הגולמיות.

רשימת התפקידים הרלוונטיים ב-IAM מופיעה במאמר תפקידים ב-Cloud Storage. לחלופין, אפשר ליצור תפקיד בהתאמה אישית עם הרשאות ספציפיות ומוגבלות.

‫API בארכיטקטורת REST

‫API בפורמט JSON

כדי להשלים את המדריך הזה באמצעות ה-API בפורמט JSON, אתם צריכים הרשאות IAM מתאימות. אם הקטגוריה שאתם רוצים לגשת אליה קיימת בפרויקט שלא אתם יצרתם, יכול להיות שתצטרכו לבקש מבעלי הפרויקט להקצות לכם תפקיד שמכיל את ההרשאות הנדרשות.

רשימת ההרשאות הנדרשות לפעולות ספציפיות מופיעה במאמר הרשאות IAM ל-methods של JSON.

רשימת התפקידים הרלוונטיים מופיעה במאמר תפקידים ב-Cloud Storage. לחלופין, אפשר ליצור תפקיד בהתאמה אישית עם הרשאות ספציפיות ומוגבלות.

העלאת אובייקט לקטגוריה

כדי להעלות אובייקט לקטגוריה:

המסוף

  1. במסוף Google Cloud, נכנסים לדף Buckets ב-Cloud Storage.

    כניסה לדף Buckets

  2. ברשימת הקטגוריות, לוחצים על שם הקטגוריה שאליה רוצים להעלות אובייקט.

  3. בכרטיסייה Objects של הקטגוריה, מבצעים אחת משתי הפעולות הבאות:

    • גוררים את הקבצים הרצויים משולחן העבודה או ממנהל הקבצים ומשחררים אותם בחלונית הראשית של מסוף Google Cloud.

    • לוחצים על הלחצן Objects, בוחרים את הקבצים שרוצים להעלות בתיבת הדו-שיח שמופיעה ולוחצים על Open.

במאמרפתרון בעיות מוסבר איך מקבלים מידע מפורט על שגיאות בפעולות Cloud Storage שנכשלו במסוף Google Cloud.

שורת הפקודה

gcloud

משתמשים בפקודה gcloud storage cp:

gcloud storage cp OBJECT_LOCATION gs://DESTINATION_BUCKET_NAME/

כאשר:

  • OBJECT_LOCATION הוא הנתיב המקומי לאובייקט. לדוגמה, Desktop/dog.png.

  • DESTINATION_BUCKET_NAME הוא שם הקטגוריה שאליה מעלים את האובייקט. לדוגמה, my-bucket.

אם הפעולה בוצעה ללא שגיאות, התשובה תיראה כמו בדוגמה הבאה:

Completed files 1/1 | 164.3kiB/164.3kiB

אפשר להגדיר מטא-נתונים של אובייקט עם מפתח קבוע או בהתאמה אישית כחלק מהעלאת האובייקט, באמצעות דגלי פקודה.

gsutil

משתמשים בפקודה gsutil cp:

gsutil cp OBJECT_LOCATION gs://DESTINATION_BUCKET_NAME/

כאשר:

  • OBJECT_LOCATION הוא הנתיב המקומי לאובייקט. לדוגמה, Desktop/dog.png.

  • DESTINATION_BUCKET_NAME הוא שם הקטגוריה שאליה מעלים את האובייקט. לדוגמה, my-bucket.

אם הפעולה בוצעה ללא שגיאות, התשובה תיראה כמו בדוגמה הבאה:

Operation completed over 1 objects/58.8 KiB.

אפשר להגדיר מטא-נתונים של אובייקט עם מפתח קבוע או בהתאמה אישית כחלק מהעלאת האובייקט בכותרות של הבקשה באמצעות האפשרות הגלובלית -h.

ספריות לקוח

C++

מידע נוסף מופיע במאמרי העזרה של Cloud Storage C++ API.

כדי לבצע אימות ב-Cloud Storage, צריך להגדיר את Application Default Credentials. מידע נוסף מופיע במאמר הגדרת אימות לסביבת פיתוח מקומית.

namespace gcs = ::google::cloud::storage;
using ::google::cloud::StatusOr;
[](gcs::Client client, std::string const& file_name,
   std::string const& bucket_name, std::string const& object_name) {
  // Note that the client library automatically computes a hash on the
  // client-side to verify data integrity during transmission.
  StatusOr<gcs::ObjectMetadata> metadata = client.UploadFile(
      file_name, bucket_name, object_name, gcs::IfGenerationMatch(0));
  if (!metadata) throw std::move(metadata).status();

  std::cout << "Uploaded " << file_name << " to object " << metadata->name()
            << " in bucket " << metadata->bucket()
            << "\nFull metadata: " << *metadata << "\n";
}

C#

מידע נוסף מופיע במאמרי העזרה של Cloud Storage C# API.

כדי לבצע אימות ב-Cloud Storage, צריך להגדיר את Application Default Credentials. מידע נוסף מופיע במאמר הגדרת אימות לסביבת פיתוח מקומית.


using Google.Cloud.Storage.V1;
using System;
using System.IO;

public class UploadFileSample
{
    public void UploadFile(
        string bucketName = "your-unique-bucket-name",
        string localPath = "my-local-path/my-file-name",
        string objectName = "my-file-name")
    {
        var storage = StorageClient.Create();
        using var fileStream = File.OpenRead(localPath);
        storage.UploadObject(bucketName, objectName, null, fileStream);
        Console.WriteLine($"Uploaded {objectName}.");
    }
}

Go

מידע נוסף מופיע במאמרי העזרה של Cloud Storage Go API.

כדי לבצע אימות ב-Cloud Storage, צריך להגדיר את Application Default Credentials. מידע נוסף מופיע במאמר הגדרת אימות לסביבת פיתוח מקומית.

import (
	"context"
	"fmt"
	"io"
	"os"
	"time"

	"cloud.google.com/go/storage"
)

// uploadFile uploads an object.
func uploadFile(w io.Writer, bucket, object string) error {
	// bucket := "bucket-name"
	// object := "object-name"
	ctx := context.Background()
	client, err := storage.NewClient(ctx)
	if err != nil {
		return fmt.Errorf("storage.NewClient: %w", err)
	}
	defer client.Close()

	// Open local file.
	f, err := os.Open("notes.txt")
	if err != nil {
		return fmt.Errorf("os.Open: %w", err)
	}
	defer f.Close()

	ctx, cancel := context.WithTimeout(ctx, time.Second*50)
	defer cancel()

	o := client.Bucket(bucket).Object(object)

	// Optional: set a generation-match precondition to avoid potential race
	// conditions and data corruptions. The request to upload is aborted if the
	// object's generation number does not match your precondition.
	// For an object that does not yet exist, set the DoesNotExist precondition.
	o = o.If(storage.Conditions{DoesNotExist: true})
	// If the live object already exists in your bucket, set instead a
	// generation-match precondition using the live object's generation number.
	// attrs, err := o.Attrs(ctx)
	// if err != nil {
	// 	return fmt.Errorf("object.Attrs: %w", err)
	// }
	// o = o.If(storage.Conditions{GenerationMatch: attrs.Generation})

	// Upload an object with storage.Writer.
	wc := o.NewWriter(ctx)
	if _, err = io.Copy(wc, f); err != nil {
		return fmt.Errorf("io.Copy: %w", err)
	}
	if err := wc.Close(); err != nil {
		return fmt.Errorf("Writer.Close: %w", err)
	}
	fmt.Fprintf(w, "Blob %v uploaded.\n", object)
	return nil
}

Java

מידע נוסף מופיע במאמרי העזרה של Cloud Storage Java API.

כדי לבצע אימות ב-Cloud Storage, צריך להגדיר את Application Default Credentials. מידע נוסף מופיע במאמר הגדרת אימות לסביבת פיתוח מקומית.


import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import java.io.IOException;
import java.nio.file.Paths;

public class UploadObject {
  public static void uploadObject(
      String projectId, String bucketName, String objectName, String filePath) throws IOException {
    // The ID of your GCP project
    // String projectId = "your-project-id";

    // The ID of your GCS bucket
    // String bucketName = "your-unique-bucket-name";

    // The ID of your GCS object
    // String objectName = "your-object-name";

    // The path to your file to upload
    // String filePath = "path/to/your/file"

    Storage storage = StorageOptions.newBuilder().setProjectId(projectId).build().getService();
    BlobId blobId = BlobId.of(bucketName, objectName);
    BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();

    // Optional: set a generation-match precondition to avoid potential race
    // conditions and data corruptions. The request returns a 412 error if the
    // preconditions are not met.
    Storage.BlobWriteOption precondition;
    if (storage.get(bucketName, objectName) == null) {
      // For a target object that does not yet exist, set the DoesNotExist precondition.
      // This will cause the request to fail if the object is created before the request runs.
      precondition = Storage.BlobWriteOption.doesNotExist();
    } else {
      // If the destination already exists in your bucket, instead set a generation-match
      // precondition. This will cause the request to fail if the existing object's generation
      // changes before the request runs.
      precondition =
          Storage.BlobWriteOption.generationMatch(
              storage.get(bucketName, objectName).getGeneration());
    }
    storage.createFrom(blobInfo, Paths.get(filePath), precondition);

    System.out.println(
        "File " + filePath + " uploaded to bucket " + bucketName + " as " + objectName);
  }
}

Node.js

מידע נוסף מופיע במאמרי העזרה של Cloud Storage Node.js API.

כדי לבצע אימות ב-Cloud Storage, צריך להגדיר את Application Default Credentials. מידע נוסף מופיע במאמר הגדרת אימות לסביבת פיתוח מקומית.

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// The ID of your GCS bucket
// const bucketName = 'your-unique-bucket-name';

// The path to your file to upload
// const filePath = 'path/to/your/file';

// The new ID for your GCS file
// const destFileName = 'your-new-file-name';

// Imports the Google Cloud client library
const {Storage} = require('@google-cloud/storage');

// Creates a client
const storage = new Storage();

async function uploadFile() {
  const options = {
    destination: destFileName,
    // Optional:
    // Set a generation-match precondition to avoid potential race conditions
    // and data corruptions. The request to upload is aborted if the object's
    // generation number does not match your precondition. For a destination
    // object that does not yet exist, set the ifGenerationMatch precondition to 0
    // If the destination object already exists in your bucket, set instead a
    // generation-match precondition using its generation number.
    preconditionOpts: {ifGenerationMatch: generationMatchPrecondition},
  };

  await storage.bucket(bucketName).upload(filePath, options);
  console.log(`${filePath} uploaded to ${bucketName}`);
}

uploadFile().catch(console.error);

PHP

מידע נוסף מופיע במאמרי העזרה של Cloud Storage PHP API.

כדי לבצע אימות ב-Cloud Storage, צריך להגדיר את Application Default Credentials. מידע נוסף מופיע במאמר הגדרת אימות לסביבת פיתוח מקומית.

use Google\Cloud\Storage\StorageClient;

/**
 * Upload a file.
 *
 * @param string $bucketName The name of your Cloud Storage bucket.
 *        (e.g. 'my-bucket')
 * @param string $objectName The name of your Cloud Storage object.
 *        (e.g. 'my-object')
 * @param string $source The path to the file to upload.
 *        (e.g. '/path/to/your/file')
 */
function upload_object(string $bucketName, string $objectName, string $source): void
{
    $storage = new StorageClient();
    $file = fopen($source, 'r');
    $bucket = $storage->bucket($bucketName);
    $object = $bucket->upload($file, [
        'name' => $objectName
    ]);
    printf('Uploaded %s to gs://%s/%s' . PHP_EOL, basename($source), $bucketName, $objectName);
}

Python

מידע נוסף מופיע במאמרי העזרה של Cloud Storage Python API.

כדי לבצע אימות ב-Cloud Storage, צריך להגדיר את Application Default Credentials. מידע נוסף מופיע במאמר הגדרת אימות לסביבת פיתוח מקומית.

from google.cloud import storage

def upload_blob(bucket_name, source_file_name, destination_blob_name):
    """Uploads a file to the bucket."""
    # The ID of your GCS bucket
    # bucket_name = "your-bucket-name"
    # The path to your file to upload
    # source_file_name = "local/path/to/file"
    # The ID of your GCS object
    # destination_blob_name = "storage-object-name"

    storage_client = storage.Client()
    bucket = storage_client.bucket(bucket_name)
    blob = bucket.blob(destination_blob_name)

    # Optional: set a generation-match precondition to avoid potential race conditions
    # and data corruptions. The request to upload is aborted if the object's
    # generation number does not match your precondition. For a destination
    # object that does not yet exist, set the if_generation_match precondition to 0.
    # If the destination object already exists in your bucket, set instead a
    # generation-match precondition using its generation number.
    generation_match_precondition = 0

    blob.upload_from_filename(source_file_name, if_generation_match=generation_match_precondition)

    print(
        f"File {source_file_name} uploaded to {destination_blob_name}."
    )

Ruby

מידע נוסף מופיע במאמרי העזרה של Cloud Storage Ruby API.

כדי לבצע אימות ב-Cloud Storage, צריך להגדיר את Application Default Credentials. מידע נוסף מופיע במאמר הגדרת אימות לסביבת פיתוח מקומית.

def upload_file bucket_name:, local_file_path:, file_name: nil
  # The ID of your GCS bucket
  # bucket_name = "your-unique-bucket-name"

  # The path to your file to upload
  # local_file_path = "/local/path/to/file.txt"

  # The ID of your GCS object
  # file_name = "your-file-name"

  require "google/cloud/storage"

  storage = Google::Cloud::Storage.new
  bucket  = storage.bucket bucket_name, skip_lookup: true

  file = bucket.create_file local_file_path, file_name

  puts "Uploaded #{local_file_path} as #{file.name} in bucket #{bucket_name}"
end

Terraform

אפשר להשתמש במשאב של Terraform כדי להעלות אובייקט. צריך לציין content או source (אפשר גם לציין את שניהם).

# Upload files
# Discussion about using tf to upload a large number of objects
# https://stackoverflow.com/questions/68455132/terraform-copy-multiple-files-to-bucket-at-the-same-time-bucket-creation

# The text object in Cloud Storage
resource "google_storage_bucket_object" "default" {
  name = "new-object"
  # Uncomment and add valid path to an object.
  #  source       = "/path/to/an/object"
  content      = "Data as string to be uploaded"
  content_type = "text/plain"
  bucket       = google_storage_bucket.static.id
}

‫API בארכיטקטורת REST

‫API בפורמט JSON

ה-API בפורמט JSON מבחין בין העלאות מדיה, שבהן הבקשה כוללת רק נתוני האובייקט, לבין העלאות מרובות חלקים ב-API בפורמט JSON, שבהן הבקשה כוללת גם את נתוני האובייקט וגם את המטא-נתונים של האובייקט.

העלאת מדיה (העלאה של בקשה אחת ללא מטא-נתונים של האובייקט)

  1. מקבלים אסימון גישה להרשאה מ-OAuth 2.0 Playground. מגדירים את ה-Playground לשימוש בפרטי הכניסה שלכם ב-OAuth. הוראות מפורטות מופיעות במאמר אימות API.
  2. משתמשים ב-cURL כדי לשלוח קריאה ל-API בפורמט JSON באמצעות בקשת אובייקט POST:

    curl -X POST --data-binary @OBJECT_LOCATION \
        -H "Authorization: Bearer OAUTH2_TOKEN" \
        -H "Content-Type: OBJECT_CONTENT_TYPE" \
        "https://storage.googleapis.com/upload/storage/v1/b/BUCKET_NAME/o?uploadType=media&name=OBJECT_NAME"

    כאשר:

    • OBJECT_LOCATION הוא הנתיב המקומי לאובייקט. לדוגמה, Desktop/dog.png.
    • OAUTH2_TOKEN הוא אסימון הגישה שיצרתם בשלב 1.
    • OBJECT_CONTENT_TYPE הוא סוג התוכן של האובייקט. לדוגמה, image/png.
    • BUCKET_NAME הוא שם הקטגוריה שאליה מעלים את האובייקט. לדוגמה, my-bucket.
    • OBJECT_NAME הוא השם שרוצים לתת לאובייקט, מותאם לקידודי התווים שמתאימים לכתובות URL. לדוגמה, pets/dog.png יותאם לקידודי התווים שמתאימים לכתובות URL באופן הבא: pets%2Fdog.png.

העלאה מרובת חלקים של API בפורמט JSON (העלאה עם בקשה אחת שכוללת מטא-נתונים של אובייקט)

  1. מקבלים אסימון גישה להרשאה מ-OAuth 2.0 Playground. מגדירים את ה-Playground לשימוש בפרטי הכניסה שלכם ב-OAuth. הוראות מפורטות מופיעות במאמר אימות API.
  2. יוצרים קובץ multipart/related שמכיל את הפרטים הבאים:

    --BOUNDARY_STRING
    Content-Type: application/json; charset=UTF-8
    
    OBJECT_METADATA
    
    --BOUNDARY_STRING
    Content-Type: OBJECT_CONTENT_TYPE
    
    OBJECT_DATA
    --BOUNDARY_STRING--

    כאשר:

    • BOUNDARY_STRING היא מחרוזת שמגדירים שמזהה את החלקים השונים של הקובץ מרובה החלקים. לדוגמה, separator_string.
    • OBJECT_METADATA הוא המטא-נתונים שרוצים לכלול בקובץ, בפורמט JSON. לכל הפחות, הקטע הזה צריך לכלול את המאפיין name של האובייקט, לדוגמה {"name": "myObject"}.
    • OBJECT_CONTENT_TYPE הוא סוג התוכן של האובייקט. לדוגמה, text/plain.
    • OBJECT_DATA הוא הנתונים בשביל האובייקט.

    לדוגמה:

    --separator_string
    Content-Type: application/json; charset=UTF-8
    
    {"name":"my-document.txt"}
    
    --separator_string
    Content-Type: text/plain
    
    This is a text file.
    --separator_string--
  3. משתמשים ב-cURL כדי לשלוח קריאה ל-API בפורמט JSON באמצעות בקשת אובייקט POST:

    curl -X POST --data-binary @MULTIPART_FILE_LOCATION \
        -H "Authorization: Bearer OAUTH2_TOKEN" \
        -H "Content-Type: multipart/related; boundary=BOUNDARY_STRING" \
        -H "Content-Length: MULTIPART_FILE_SIZE" \
        "https://storage.googleapis.com/upload/storage/v1/b/BUCKET_NAME/o?uploadType=multipart"

    כאשר:

    • MULTIPART_FILE_LOCATION הוא הנתיב המקומי לקובץ מרובה החלקים שנוצר בשלב 2. לדוגמה, Desktop/my-upload.multipart.
    • OAUTH2_TOKEN הוא אסימון הגישה שיצרתם בשלב 1.
    • BOUNDARY_STRING הוא מחרוזת הגבולות שיצרתם בשלב 2. לדוגמה, my-boundary.
    • MULTIPART_FILE_SIZE הוא הגודל הכולל בבייטים של הקובץ מרובה החלקים שיצרתם בשלב 2. לדוגמה, 2000000.
    • BUCKET_NAME הוא שם הקטגוריה שאליה מעלים את האובייקט. לדוגמה, my-bucket.

אם הבקשה תתבצע בהצלחה, השרת יחזיר את קוד הסטטוס 200 OK של HTTP יחד עם המטא-נתונים של הקובץ.

‫API בפורמט XML

  1. מקבלים אסימון גישה להרשאה מ-OAuth 2.0 Playground. מגדירים את ה-Playground לשימוש בפרטי הכניסה שלכם ב-OAuth. הוראות מפורטות מופיעות במאמר אימות API.
  2. משתמשים ב-cURL כדי לשלוח קריאה ל-API בפורמט XML באמצעות בקשת אובייקט PUT:

    curl -X PUT --data-binary @OBJECT_LOCATION \
        -H "Authorization: Bearer OAUTH2_TOKEN" \
        -H "Content-Type: OBJECT_CONTENT_TYPE" \
        "https://storage.googleapis.com/BUCKET_NAME/OBJECT_NAME"

    כאשר:

    • OBJECT_LOCATION הוא הנתיב המקומי לאובייקט. לדוגמה, Desktop/dog.png.
    • OAUTH2_TOKEN הוא אסימון הגישה שיצרתם בשלב 1.
    • OBJECT_CONTENT_TYPE הוא סוג התוכן של האובייקט. לדוגמה, image/png.
    • BUCKET_NAME הוא שם הקטגוריה שאליה מעלים את האובייקט. לדוגמה, my-bucket.
    • OBJECT_NAME הוא השם שרוצים לתת לאובייקט, מותאם לקידודי התווים שמתאימים לכתובות URL. לדוגמה, pets/dog.png יותאם לקידודי התווים שמתאימים לכתובות URL באופן הבא: pets%2Fdog.png.

אפשר להגדיר מטא-נתונים של אובייקט נוספים כחלק מהעלאת האובייקט בכותרות של הבקשה, באותו אופן שבו קבוצות הדוגמאות שלמעלה מגדירות את Content-Type. כשעובדים עם API בפורמט XML, אפשר להגדיר מטא-נתונים רק בזמן כתיבת האובייקט, למשל בהעלאה, בהעתקה או בהחלפה של האובייקט. למידע נוסף, עיינו במאמר עריכת מטא-נתונים של אובייקטים.

המאמרים הבאים

נסו בעצמכם

אנחנו ממליצים למשתמשים חדשים ב-Google Cloud ליצור חשבון כדי להעריך את הביצועים של מוצרי Cloud Storage בתרחישים מהעולם האמיתי. לקוחות חדשים גם מקבלים בחינם קרדיט בשווי 300$ להרצה, לבדיקה ולפריסה של עומסי העבודה.

נסו את Cloud Storage בחינם