ingestr v1.1.55 - HubSpot Destination and BigQuery Staging


ingestr v1.1.55 was published on 22 September 2026. The release adds a HubSpot destination for reverse ETL, writing CRM objects and associations from any ingestr source. It also stops BigQuery from creating _bruin_staging in US when the target dataset lives in another region and location is unset.

The full release notes and downloads are on the GitHub release page. The compare from the previous tag is v1.1.54 to v1.1.55. This tag is not a prerelease.

Pull request 1203 adds the destination. Docs are in docs/supported-sources/hubspot.md. id_property is a HubSpot internal name, not a display label. --primary-key is the source column to match. --columns uses internal names on the destination side and errors when the source column is missing.

merge and append upsert or create and require --primary-key. update and delete address existing records. When the source column and id_property are both omitted, each defaults to hs_object_id. replace upserts the source and archives HubSpot records that are absent, including records with no key. Associations use a table named obj1+obj2.

Batch writes are not atomic. A record level 400 or 409 bisects the batch: valid rows land, and the bad row is isolated. A live run of 560 rows with one bad row wrote 559 and rejected 1. --reject-mode is fail, fail_fast, or skip. fail and skip write every valid row. fail_fast stops at the first bad row and leaves the rest unwritten. skip prints rejected identifiers at the end. resolveKeysToIDs chunks batch/read at 100 inputs. --write-nulls clears properties by default and applies only to strategies that write them.

A 404 from batch/read means no records only when category is OBJECT_NOT_FOUND. Other 404s fail the run, including under --reject-mode=skip. Labeled deletes call v4 labels/archive and remove only that label. A numeric association_type without a category is looked up at connect time, not assumed to be HUBSPOT_DEFINED. An empty replace does not archive. The sweep needs a batch that contains rows, so a zero row extract skips it. To delete on purpose, use --incremental-strategy delete.

With no location on the BigQuery URI, _bruin_staging was created in US. Pull request 1201 uses the target region instead. A target in EU failed the CTAS swap:

Error 404: Not found: Dataset <project>:_bruin_staging was not found in location EU

The miss is the replace fast path from a database source with a primary key, and it also hits storage_write.

resolveLocation reads the target dataset location once. effectiveLocation returns the configured location, or that resolved value. Staging and the load, copy, and query jobs use it. PrepareOptions.TargetTable tells replace which target to resolve, because that strategy prepares only the staging table. A lookup error other than a missing dataset fails in PrepareTable, so permission denied no longer creates a silent US dataset. A 404 for a target that does not exist yet still leaves the first dataset on the US default.

Pull request 1202 checks staging that already exists. validateDatasetLocation compares it to the effective location, using US when nothing else resolves. A _bruin_staging in another region fails before the load. One dataset id exists in only one region per project, so several regions need a --staging-dataset each. A live EU target with no location created staging in EU and finished the storage_write swap.

gosnowflake logged probes that ingestr expects to fail. Pull request 1195 quiets them. The driver writes every failed query at error on stderr inside queryContextInternal, then returns the error. GetMaxCDCLSN returns an empty CDC position when the table is missing. GetTableSchema returns nil so the load can create it. A first run still printed SQL state 42S02, code 002003, and “does not exist or not authorized”, then loaded rows.

ConfigureDriverLogging in pkg/snowflake raises the driver level above error at connect. The destination, the source native reader, and OpenDB call it. Handled failures still surface as returned errors and LogFailedQuery. --debug sets the level to debug. SetLogLevel("OFF") does not silence WithContext(ctx).Errorf, because that logrus entry uses the inner level, not the enabled flag. A Snowflake client config file is applied while the connection is built and still wins. Stderr alerts go quiet on first runs, and they miss driver errors ingestr does not rewrap unless the job passes --debug.

Pull request 1194 sets google.golang.org/grpc to 1.83.2 for GHSA-2v4p-qf9q-27wj. Pull request 1198 sets cilium/ebpf to 0.22.0 for CVE-2026-10722. Pull request 1197 sets go-pkcs12 to 0.7.2 for GHSA-mpwr-8vm7-h73f. Pull request 1196 sets vitess.io/vitess to v0.24.3 for GHSA-mhc4-g3wh-cw7m. The notes do not name an ingest behavior change from these pins.

Pull request 1193 pulls MinIO from quay.io in CI. The Docker Hub image the workflow used was removed. Object storage calls in a pipeline are unchanged.

No item is marked breaking. With no BigQuery location, _bruin_staging follows the target region, and an existing dataset in another region fails before the load. Set --staging-dataset per region. An explicit location matches v1.1.54.

HubSpot replace does not archive a zero row source. Use --incremental-strategy delete to remove records. fail_fast stops after the first bad row, and a batch can apply in part.

Snowflake omits the missing table probes. Pass --debug to see driver lines again. A client config file overrides ConfigureDriverLogging.