risingwavelabs/risingwave · error · SinkError::Config

gcs.service.account is required with Google Cloud Storage (G

Error message

gcs.service.account is required with Google Cloud Storage (GCS)

What it means

To open a GCS-backed DeltaLake table, the connector requires the gcs.service.account option (a service account key). If DeltaTableUrl::Gcs was detected but gcs_service_account is None, it raises SinkError::Config because the GCS handler cannot authenticate without credentials.

Source

Thrown at src/connector/src/sink/deltalake.rs:116

    pub async fn create_deltalake_client(&self) -> Result<DeltaTable> {
        let table = match Self::get_table_url(&self.location)? {
            DeltaTableUrl::S3(s3_path) => {
                let storage_options = self.build_delta_lake_config_for_aws().await?;
                deltalake::aws::register_handlers(None);
                let url = Url::parse(&s3_path).map_err(|e| SinkError::DeltaLake(anyhow!(e)))?;
                deltalake::open_table_with_storage_options(url, storage_options).await?
            }
            DeltaTableUrl::Local(local_path) => {
                let url = Url::parse(&format!("file://{}", local_path))
                    .map_err(|e| SinkError::DeltaLake(anyhow!(e)))?;
                deltalake::open_table(url).await?
            }
            DeltaTableUrl::Gcs(gcs_path) => {
                let mut storage_options = HashMap::new();
                storage_options.insert(
                    GCS_SERVICE_ACCOUNT.to_owned(),
                    self.gcs_service_account.clone().ok_or_else(|| {
                        SinkError::Config(anyhow!(
                            "gcs.service.account is required with Google Cloud Storage (GCS)"
                        ))
                    })?,
                );
                deltalake::gcp::register_handlers(None);
                let url = Url::parse(&gcs_path).map_err(|e| SinkError::DeltaLake(anyhow!(e)))?;
                deltalake::open_table_with_storage_options(url, storage_options).await?
            }
        };
        Ok(table)
    }

    fn get_table_url(path: &str) -> Result<DeltaTableUrl> {
        if path.starts_with("s3://") || path.starts_with("s3a://") {
            Ok(DeltaTableUrl::S3(path.to_owned()))
        } else if path.starts_with("gs://") {
            Ok(DeltaTableUrl::Gcs(path.to_owned()))
        } else if let Some(path) = path.strip_prefix("file://") {

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Add the option: gcs.service.account = '<service account JSON>' to the sink's deltalake options.
  2. Verify the option key spelling is exactly 'gcs.service.account'.
  3. Ensure the provided service account JSON is valid and has access to the bucket.

Example fix

// before
CREATE SINK s INTO DELTALAKE LOCATION = 'gs://bucket/table';
// after
CREATE SINK s INTO DELTALAKE LOCATION = 'gs://bucket/table'
  WITH (connector = 'deltalake', gcs.service.account = '{"type":"service_account",...}');
Defensive patterns

Strategy: validation

Validate before calling

if location.starts_with("gs://") && !options.contains_key("gcs.service.account") {
    return Err("gcs.service.account is required for gs:// deltalake locations");
}

Prevention

When it happens

Trigger: create_deltalake_client with a location starting with gs:// while the sink definition omits the 'gcs.service.account' option (it was not provided or was null).

Common situations: Creating a deltalake sink pointing at gs:// but forgetting the service account JSON; providing credentials under a wrong option name; assuming default credential chains work when the connector requires the explicit key.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/a98d9ae102fd223b. Report an issue: GitHub.