Repository: chef/automate
Pull request #4759 (2021-02-22 19:10:59)

PG: Ignore comments on extensions when restoring
### :nut_and_bolt: Description: What code changed, and why?

A customer is experiencing errors attempting to restore a postures database using AWS RDS. The pertinent part of the error is like this (there are other errors for other extensions) `could not execute query: ERROR:  must be owner of extension pgcrypto\n    Command was: COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';`.

This error happens because in AWS RDS, the superuser is actually quite limited. If you start an interactive database session manually with an RDS instance, you can see that `COMMENT ON EXTENSION ...` commands won't work.

To fix the restore issue, we want to avoid attempting to restore these comments, as they do not affect functionality. [`pg_dump` version 12.x](https://www.postgresql.org/docs/12/app-pgdump.html) and [`pg_restore` version 12.x](https://www.postgresql.org/docs/12/app-pgrestore.html) support a `--no-comments` filter, but we are on version 9.6.11, which does not have this feature. Instead we must implement similar functionality by analyzing the TOC of the pg dump files and excluding the entries that we wish to ignore.

In the process of adding to it, I was able to extract existing functionality that filters the TOC before restore to a unit-test-able form. It would be ideal to add backup and restore of a system with external data stores to our acceptance procedure or automated pipelines, but that is a more involved effort.

### :chains: Related Resources

https://github.com/chef/customer-bugs/issues/303

### :+1: Definition of Done

Can restore backups to a new database when using AWS RDS.

### :athletic_shoe: How to Build and Test the Change

This one is hard to test! @balkarsinghkang set up a cluster to repro this following the instructions here: https://github.com/chef-cft/chef-examples/blob/master/examples/a2-aws-backends/a2-aws-backends.md Once the cluster is up, create a backup and get the ID of it. Then you need to attempt a restore to a NEW RDS database, using a patch config, something like `chef-automate backup restore BACKUP_URI --patch-config config.toml`. This will fail with the bug we are fixing, with something like this in the logs:

```
Jan 28 17:47:55 ip-10-100-101-111 hab[9622]: deployment-service.default(O): time="2021-01-28T17:47:55Z" level=error msg="Failed to restore synchronous operations" backup_id=20210128174154 error="failed to import database dump from authz-service/pg_data/chef_authz_service.fc: error importing database \"chef_authz_service\": failed to import SQL file from \"stdin\", stderr: pg_restore: [archiver (db)] Error while PROCESSING TOC:\npg_restore: [archiver (db)] Error from TOC entry 3357; 0 0 COMMENT EXTENSION pgcrypto \npg_restore: [archiver (db)] could not execute query: ERROR:  must be owner of extension pgcrypto\n    Command was: COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';\n\n\n\npg_restore: [archiver (db)] Error from TOC entry 3358; 0 0 COMMENT EXTENSION \"uuid-ossp\" \npg_restore: [archiver (db)] could not execute query: ERROR:  must be owner of extension uuid-ossp\n    Command was: COMMENT ON EXTENSION \"uuid-ossp\" IS 'generate universally unique identifiers (UUIDs)';\n\n\n\nWARNING: errors ignored on restore: 2\n: exit status 1" restore_id=20210128174459
Jan 28 17:47:55 ip-10-100-101-111 hab[9622]: deployment-service.default(O): time="2021-01-28T17:47:55Z" level=error msg="Failed to restore services" backup_id=20210128174154 error="failed to import database dump from authz-service/pg_data/chef_authz_service.fc: error importing database \"chef_authz_service\": failed to import SQL file from \"stdin\", stderr: pg_restore: [archiver (db)] Error while PROCESSING TOC:\npg_restore: [archiver (db)] Error from TOC entry 3357; 0 0 COMMENT EXTENSION pgcrypto \npg_restore: [archiver (db)] could not execute query: ERROR:  must be owner of extension pgcrypto\n    Command was: COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';\n\n\n\npg_restore: [archiver (db)] Error from TOC entry 3358; 0 0 COMMENT EXTENSION \"uuid-ossp\" \npg_restore: [archiver (db)] could not execute query: ERROR:  must be owner of extension uuid-ossp\n    Command was: COMMENT ON EXTENSION \"uuid-ossp\" IS 'generate universally unique identifiers (UUIDs)';\n\n\n\nWARNING: errors ignored on restore: 2\n: exit status 1" restore_id=20210128174459
```

To see this change work, you need to build the deployment service locally and then copy it over to your test machine. From there you need to cache all of the automate component artifacts. I did `for p in $(ls -d -1 chef/*); do hab pkg download --channel current --download-directory /root/hartifacts/ $p; done` and then ended up moving all the `.hart` files over to `/hab/cache/artifacts/`. Copy also your custom deployment service to `/hab/cache/artifacts/`. From there, you can run this command to do the restore:

```
chef-automate backup restore s3://balkar/backups/automate/20210218183956 --patch-config config.toml --override-origin YOURORIGIN --hartifacts /hab/cache/artifacts/ --skip-preflight --upgrade
```




netlify[bot]: Deploy preview for *chef-automate* processing.

Building with commit 7042292c574a9e6baaf5199c226d8d371e39f80d

https://app.netlify.com/sites/chef-automate/deploys/603401c4913b06000788a132

Patch:
diff --git a/api/interservice/cds/service/cds.pb.go b/api/interservice/cds/service/cds.pb.go
index fbf960e99..bdfdda372 100644
--- a/api/interservice/cds/service/cds.pb.go
+++ b/api/interservice/cds/service/cds.pb.go
@@ -38,8 +38,8 @@ var file_interservice_cds_service_cds_proto_rawDesc = []byte{
 	0x0a, 0x22, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x63,
 	0x64, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x63, 0x64, 0x73, 0x2e, 0x70,
 	0x72, 0x6f, 0x74, 0x6f, 0x12, 0x29, 0x63, 0x68, 0x65, 0x66, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6d,
-	0x61, 0x74, 0x65, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x61,
-	0x6d, 0x74, 0x65, 0x5f, 0x63, 0x64, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x1a,
+	0x61, 0x74, 0x65, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6d,
+	0x61, 0x74, 0x65, 0x5f, 0x63, 0x64, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x1a,
 	0x1f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x64, 0x73, 0x2f, 0x72, 0x65,
 	0x71, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 	0x1a, 0x20, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x64, 0x73, 0x2f, 0x72,
@@ -96,7 +96,7 @@ var file_interservice_cds_service_cds_proto_rawDesc = []byte{
 	0x74, 0x69, 0x61, 0x6c, 0x73, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
 	0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x65, 0x66, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74,
 	0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x69,
-	0x63, 0x65, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x61, 0x6d, 0x74, 0x65, 0x5f, 0x63, 0x64, 0x73, 0x2f,
+	0x63, 0x65, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x64, 0x73, 0x2f,
 	0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
@@ -115,18 +115,18 @@ var file_interservice_cds_service_cds_proto_goTypes = []interface{}{
 	(*response.Credentials)(nil),        // 11: chef.automate.api.cds.response.Credentials
 }
 var file_interservice_cds_service_cds_proto_depIdxs = []int32{
-	0,  // 0: chef.automate.domain.autoamte_cds.service.AutomateCdsService.GetVersion:input_type -> chef.automate.api.common.version.VersionInfoRequest
-	1,  // 1: chef.automate.domain.autoamte_cds.service.AutomateCdsService.ListContentItems:input_type -> chef.automate.api.cds.request.ContentItems
-	2,  // 2: chef.automate.domain.autoamte_cds.service.AutomateCdsService.InstallContentItem:input_type -> chef.automate.api.cds.request.InstallContentItem
-	3,  // 3: chef.automate.domain.autoamte_cds.service.AutomateCdsService.DownloadContentItem:input_type -> chef.automate.api.cds.request.DownloadContentItem
-	4,  // 4: chef.automate.domain.autoamte_cds.service.AutomateCdsService.IsContentEnabled:input_type -> chef.automate.api.cds.request.ContentEnabled
-	5,  // 5: chef.automate.domain.autoamte_cds.service.AutomateCdsService.SubmitCredentials:input_type -> chef.automate.api.cds.request.Credentials
-	6,  // 6: chef.automate.domain.autoamte_cds.service.AutomateCdsService.GetVersion:output_type -> chef.automate.api.common.version.VersionInfo
-	7,  // 7: chef.automate.domain.autoamte_cds.service.AutomateCdsService.ListContentItems:output_type -> chef.automate.api.cds.response.ContentItems
-	8,  // 8: chef.automate.domain.autoamte_cds.service.AutomateCdsService.InstallContentItem:output_type -> chef.automate.api.cds.response.InstallContentItem
-	9,  // 9: chef.automate.domain.autoamte_cds.service.AutomateCdsService.DownloadContentItem:output_type -> chef.automate.api.common.ExportData
-	10, // 10: chef.automate.domain.autoamte_cds.service.AutomateCdsService.IsContentEnabled:output_type -> chef.automate.api.cds.response.ContentEnabled
-	11, // 11: chef.automate.domain.autoamte_cds.service.AutomateCdsService.SubmitCredentials:output_type -> chef.automate.api.cds.response.Credentials
+	0,  // 0: chef.automate.domain.automate_cds.service.AutomateCdsService.GetVersion:input_type -> chef.automate.api.common.version.VersionInfoRequest
+	1,  // 1: chef.automate.domain.automate_cds.service.AutomateCdsService.ListContentItems:input_type -> chef.automate.api.cds.request.ContentItems
+	2,  // 2: chef.automate.domain.automate_cds.service.AutomateCdsService.InstallContentItem:input_type -> chef.automate.api.cds.request.InstallContentItem
+	3,  // 3: chef.automate.domain.automate_cds.service.AutomateCdsService.DownloadContentItem:input_type -> chef.automate.api.cds.request.DownloadContentItem
+	4,  // 4: chef.automate.domain.automate_cds.service.AutomateCdsService.IsContentEnabled:input_type -> chef.automate.api.cds.request.ContentEnabled
+	5,  // 5: chef.automate.domain.automate_cds.service.AutomateCdsService.SubmitCredentials:input_type -> chef.automate.api.cds.request.Credentials
+	6,  // 6: chef.automate.domain.automate_cds.service.AutomateCdsService.GetVersion:output_type -> chef.automate.api.common.version.VersionInfo
+	7,  // 7: chef.automate.domain.automate_cds.service.AutomateCdsService.ListContentItems:output_type -> chef.automate.api.cds.response.ContentItems
+	8,  // 8: chef.automate.domain.automate_cds.service.AutomateCdsService.InstallContentItem:output_type -> chef.automate.api.cds.response.InstallContentItem
+	9,  // 9: chef.automate.domain.automate_cds.service.AutomateCdsService.DownloadContentItem:output_type -> chef.automate.api.common.ExportData
+	10, // 10: chef.automate.domain.automate_cds.service.AutomateCdsService.IsContentEnabled:output_type -> chef.automate.api.cds.response.ContentEnabled
+	11, // 11: chef.automate.domain.automate_cds.service.AutomateCdsService.SubmitCredentials:output_type -> chef.automate.api.cds.response.Credentials
 	6,  // [6:12] is the sub-list for method output_type
 	0,  // [0:6] is the sub-list for method input_type
 	0,  // [0:0] is the sub-list for extension type_name
@@ -188,7 +188,7 @@ func NewAutomateCdsServiceClient(cc grpc.ClientConnInterface) AutomateCdsService
 
 func (c *automateCdsServiceClient) GetVersion(ctx context.Context, in *version.VersionInfoRequest, opts ...grpc.CallOption) (*version.VersionInfo, error) {
 	out := new(version.VersionInfo)
-	err := c.cc.Invoke(ctx, "/chef.automate.domain.autoamte_cds.service.AutomateCdsService/GetVersion", in, out, opts...)
+	err := c.cc.Invoke(ctx, "/chef.automate.domain.automate_cds.service.AutomateCdsService/GetVersion", in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -197,7 +197,7 @@ func (c *automateCdsServiceClient) GetVersion(ctx context.Context, in *version.V
 
 func (c *automateCdsServiceClient) ListContentItems(ctx context.Context, in *request.ContentItems, opts ...grpc.CallOption) (*response.ContentItems, error) {
 	out := new(response.ContentItems)
-	err := c.cc.Invoke(ctx, "/chef.automate.domain.autoamte_cds.service.AutomateCdsService/ListContentItems", in, out, opts...)
+	err := c.cc.Invoke(ctx, "/chef.automate.domain.automate_cds.service.AutomateCdsService/ListContentItems", in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -206,7 +206,7 @@ func (c *automateCdsServiceClient) ListContentItems(ctx context.Context, in *req
 
 func (c *automateCdsServiceClient) InstallContentItem(ctx context.Context, in *request.InstallContentItem, opts ...grpc.CallOption) (*response.InstallContentItem, error) {
 	out := new(response.InstallContentItem)
-	err := c.cc.Invoke(ctx, "/chef.automate.domain.autoamte_cds.service.AutomateCdsService/InstallContentItem", in, out, opts...)
+	err := c.cc.Invoke(ctx, "/chef.automate.domain.automate_cds.service.AutomateCdsService/InstallContentItem", in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -214,7 +214,7 @@ func (c *automateCdsServiceClient) InstallContentItem(ctx context.Context, in *r
 }
 
 func (c *automateCdsServiceClient) DownloadContentItem(ctx context.Context, in *request.DownloadContentItem, opts ...grpc.CallOption) (AutomateCdsService_DownloadContentItemClient, error) {
-	stream, err := c.cc.NewStream(ctx, &_AutomateCdsService_serviceDesc.Streams[0], "/chef.automate.domain.autoamte_cds.service.AutomateCdsService/DownloadContentItem", opts...)
+	stream, err := c.cc.NewStream(ctx, &_AutomateCdsService_serviceDesc.Streams[0], "/chef.automate.domain.automate_cds.service.AutomateCdsService/DownloadContentItem", opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -247,7 +247,7 @@ func (x *automateCdsServiceDownloadContentItemClient) Recv() (*common.ExportData
 
 func (c *automateCdsServiceClient) IsContentEnabled(ctx context.Context, in *request.ContentEnabled, opts ...grpc.CallOption) (*response.ContentEnabled, error) {
 	out := new(response.ContentEnabled)
-	err := c.cc.Invoke(ctx, "/chef.automate.domain.autoamte_cds.service.AutomateCdsService/IsContentEnabled", in, out, opts...)
+	err := c.cc.Invoke(ctx, "/chef.automate.domain.automate_cds.service.AutomateCdsService/IsContentEnabled", in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -256,7 +256,7 @@ func (c *automateCdsServiceClient) IsContentEnabled(ctx context.Context, in *req
 
 func (c *automateCdsServiceClient) SubmitCredentials(ctx context.Context, in *request.Credentials, opts ...grpc.CallOption) (*response.Credentials, error) {
 	out := new(response.Credentials)
-	err := c.cc.Invoke(ctx, "/chef.automate.domain.autoamte_cds.service.AutomateCdsService/SubmitCredentials", in, out, opts...)
+	err := c.cc.Invoke(ctx, "/chef.automate.domain.automate_cds.service.AutomateCdsService/SubmitCredentials", in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -310,7 +310,7 @@ func _AutomateCdsService_GetVersion_Handler(srv interface{}, ctx context.Context
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: "/chef.automate.domain.autoamte_cds.service.AutomateCdsService/GetVersion",
+		FullMethod: "/chef.automate.domain.automate_cds.service.AutomateCdsService/GetVersion",
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
 		return srv.(AutomateCdsServiceServer).GetVersion(ctx, req.(*version.VersionInfoRequest))
@@ -328,7 +328,7 @@ func _AutomateCdsService_ListContentItems_Handler(srv interface{}, ctx context.C
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: "/chef.automate.domain.autoamte_cds.service.AutomateCdsService/ListContentItems",
+		FullMethod: "/chef.automate.domain.automate_cds.service.AutomateCdsService/ListContentItems",
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
 		return srv.(AutomateCdsServiceServer).ListContentItems(ctx, req.(*request.ContentItems))
@@ -346,7 +346,7 @@ func _AutomateCdsService_InstallContentItem_Handler(srv interface{}, ctx context
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: "/chef.automate.domain.autoamte_cds.service.AutomateCdsService/InstallContentItem",
+		FullMethod: "/chef.automate.domain.automate_cds.service.AutomateCdsService/InstallContentItem",
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
 		return srv.(AutomateCdsServiceServer).InstallContentItem(ctx, req.(*request.InstallContentItem))
@@ -385,7 +385,7 @@ func _AutomateCdsService_IsContentEnabled_Handler(srv interface{}, ctx context.C
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: "/chef.automate.domain.autoamte_cds.service.AutomateCdsService/IsContentEnabled",
+		FullMethod: "/chef.automate.domain.automate_cds.service.AutomateCdsService/IsContentEnabled",
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
 		return srv.(AutomateCdsServiceServer).IsContentEnabled(ctx, req.(*request.ContentEnabled))
@@ -403,7 +403,7 @@ func _AutomateCdsService_SubmitCredentials_Handler(srv interface{}, ctx context.
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: "/chef.automate.domain.autoamte_cds.service.AutomateCdsService/SubmitCredentials",
+		FullMethod: "/chef.automate.domain.automate_cds.service.AutomateCdsService/SubmitCredentials",
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
 		return srv.(AutomateCdsServiceServer).SubmitCredentials(ctx, req.(*request.Credentials))
@@ -412,7 +412,7 @@ func _AutomateCdsService_SubmitCredentials_Handler(srv interface{}, ctx context.
 }
 
 var _AutomateCdsService_serviceDesc = grpc.ServiceDesc{
-	ServiceName: "chef.automate.domain.autoamte_cds.service.AutomateCdsService",
+	ServiceName: "chef.automate.domain.automate_cds.service.AutomateCdsService",
 	HandlerType: (*AutomateCdsServiceServer)(nil),
 	Methods: []grpc.MethodDesc{
 		{
diff --git a/api/interservice/cds/service/cds.proto b/api/interservice/cds/service/cds.proto
index 07d76a7ed..ac2c0d741 100644
--- a/api/interservice/cds/service/cds.proto
+++ b/api/interservice/cds/service/cds.proto
@@ -1,7 +1,7 @@
 syntax = "proto3";
 
-package chef.automate.domain.autoamte_cds.service;
-option go_package = "github.com/chef/automate/api/interservice/autoamte_cds/service";
+package chef.automate.domain.automate_cds.service;
+option go_package = "github.com/chef/automate/api/interservice/automate_cds/service";
 
 import "external/cds/request/root.proto";
 import "external/cds/response/root.proto";
diff --git a/buf.yaml b/buf.yaml
index 9764f1574..4ae231a72 100644
--- a/buf.yaml
+++ b/buf.yaml
@@ -415,7 +415,8 @@ lint:
     - external/habitat/event.proto # this file is owned by habitat and copied here
 breaking:
   use:
-    - PACKAGE
+    # TODO: TEMPORARY CHANGE JUST FOR THIS PR!
+    - FILE
   ignore:
     # Changes to interservice APIs are okay until (if ever) we support clusters
     # of interconnected internal services that would not upgrade in unison
diff --git a/lib/platform/pg/exporter.go b/lib/platform/pg/exporter.go
index ec54df9d0..5c3ec6b60 100644
--- a/lib/platform/pg/exporter.go
+++ b/lib/platform/pg/exporter.go
@@ -295,7 +295,7 @@ func (db DatabaseExporter) buildSQLTOC(pgBackupFile string, filters []string) (f
 		scanner := bufio.NewScanner(reader)
 		for scanner.Scan() {
 			txt := scanner.Text()
-			if !strings.Contains(txt, "2615 2200") {
+			if !IsIncompatibleTOCLine(txt) {
 				_, err := fmt.Fprintln(pgListFile, txt)
 				if err != nil {
 					errChan <- err
@@ -328,6 +328,34 @@ func (db DatabaseExporter) buildSQLTOC(pgBackupFile string, filters []string) (f
 	return pgListFile.Name(), func() { removeFile(pgListFile.Name()) }, nil
 }
 
+// isIncompatibleTOCLine takes in a line from TOC of a pg backup and decides if
+// it should be excluded from the restore attempt. Some items in a backup
+// cannot be restored in (e.g.) AWS RDS, where the superuser is not all that
+// "super."
+//
+// To gather the data you will likely need to modify this function, you first
+// need to make a database dump in the custom format. The command for that is
+// of the form `pg_dump DB_URI --format c > database_dump.fc`. To view the TOC
+// of that file, use a command like `pg_restore --list database_dump.fc`
+//
+// If using the habitat dev studio, you can access the postgresql CLI tools via
+// `hab pkg exec core/postgresql-client COMMAND`
+func IsIncompatibleTOCLine(line string) bool {
+	// I think this refers to a TOC line like this:
+	//   4; 2615 2200 SCHEMA - public automate
+	if strings.Contains(line, "2615 2200") {
+		return true
+	}
+	// This refers to TOC lines like this:
+	//   2214; 0 0 COMMENT - SCHEMA public automate
+	// AWS RDS superuser cannot run comands like this:
+	//   COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';
+	if strings.Contains(line, "COMMENT - EXTENSION") {
+		return true
+	}
+	return false
+}
+
 func (db DatabaseExporter) restoreCustomFile(exitOnError bool) error {
 	filters := []string{"--no-owner", "--no-acl"}
 
@@ -374,7 +402,7 @@ func (db DatabaseExporter) restoreCustomFile(exitOnError bool) error {
 			command.Stderr(stderrBuff),
 			command.Timeout(db.Timeout))...)
 	if err != nil {
-		return errors.Wrapf(err, "failed to import SQL file from %q, stderr: %s", source, stderrBuff.String())
+		return errors.Wrapf(err, "failed to import FC format file from %q, stderr: %s", source, stderrBuff.String())
 	}
 	logrus.WithField("stderr", stderrBuff.String()).Debug("psql import complete")
 	return nil


Test patch:
diff --git a/lib/platform/pg/exporter_test.go b/lib/platform/pg/exporter_test.go
index 015afd201..95b7f25f8 100644
--- a/lib/platform/pg/exporter_test.go
+++ b/lib/platform/pg/exporter_test.go
@@ -338,4 +338,39 @@ func TestImport(t *testing.T) {
 		require.Error(t, err)
 	})
 
+	t.Run("it filters items from the TOC that cannot be imported", func(t *testing.T) {
+		rejectedTOCLines := []string{
+			"4; 2615 2200 SCHEMA - public automate",
+			"2215; 0 0 COMMENT - EXTENSION plpgsql ",
+			"2216; 0 0 COMMENT - EXTENSION pgcrypto ",
+		}
+
+		for _, line := range rejectedTOCLines {
+			assert.True(t, pg.IsIncompatibleTOCLine(line), "line %q should return true for IsIncompatibleTOCLine()", line)
+		}
+
+		acceptedTOCLines := []string{
+			"2213; 1262 17864 DATABASE - notifications_service notifications",
+			"2214; 0 0 COMMENT - SCHEMA public automate",
+			"1; 3079 12415 EXTENSION - plpgsql ",
+			"2; 3079 18164 EXTENSION - pgcrypto ",
+			"600; 1247 21250 TYPE public rule_action notifications",
+			"597; 1247 21238 TYPE public rule_event notifications",
+			"237; 1255 21273 FUNCTION public log_and_clean_event(character varying, public.rule_event, smallint) notifications",
+			"188; 1259 21309 TABLE public processed_events notifications",
+			"187; 1259 21299 TABLE public rules notifications",
+			"186; 1259 21286 TABLE public schema_migrations notifications",
+			"2207; 0 21309 TABLE DATA public processed_events notifications",
+			"2206; 0 21299 TABLE DATA public rules notifications",
+			"2205; 0 21286 TABLE DATA public schema_migrations notifications",
+			"2087; 2606 21314 CONSTRAINT public processed_events processed_events_pkey notifications",
+			"2083; 2606 21308 CONSTRAINT public rules rules_name_key notifications",
+			"2085; 2606 21306 CONSTRAINT public rules rules_pkey notifications",
+			"2081; 2606 21290 CONSTRAINT public schema_migrations schema_migrations_pkey notifications",
+		}
+
+		for _, line := range acceptedTOCLines {
+			assert.False(t, pg.IsIncompatibleTOCLine(line), "line %q should return false for IsIncompatibleTOCLine()", line)
+		}
+	})
 }