Skip to content
Open
Binary file modified api/descriptor.bin
Binary file not shown.
66 changes: 48 additions & 18 deletions api/dump/v1beta1/dump.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions api/dump/v1beta1/dump.pb.validate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions api/dump/v1beta1/dump.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ message Dump {
google.protobuf.Timestamp start_time = 4;
google.protobuf.Timestamp end_time = 5;
google.protobuf.Timestamp created_at = 7;
// This field is set to true if the dump was created with encryption enabled, and false otherwise.
bool encrypted = 8;
}

message StartDumpRequest {
Expand All @@ -30,6 +32,10 @@ message StartDumpRequest {
google.protobuf.Timestamp end_time = 3;
bool export_qan = 4;
bool ignore_load = 5;
// If true, the dump will be encrypted. Note that enabling encryption may increase the time required to create the dump.
bool enable_encryption = 6;
// The password used for encryption. It must be at least 8 characters long. This field is required if enable_encryption is true.
string encryption_password = 7;
}

message StartDumpResponse {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions api/dump/v1beta1/json/v1beta1.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
"type": "string",
"format": "date-time",
"x-order": 5
},
"encrypted": {
"description": "This field is set to true if the dump was created with encryption enabled, and false otherwise.",
"type": "boolean",
"x-order": 6
}
}
},
Expand Down Expand Up @@ -314,6 +319,16 @@
"ignore_load": {
"type": "boolean",
"x-order": 4
},
"enable_encryption": {
"description": "If true, the dump will be encrypted. Note that enabling encryption may increase the time required to create the dump.",
"type": "boolean",
"x-order": 5
},
"encryption_password": {
"description": "The password used for encryption. It must be at least 8 characters long. This field is required if enable_encryption is true.",
"type": "string",
"x-order": 6
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions api/swagger/swagger-dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -4763,6 +4763,11 @@
"type": "string",
"format": "date-time",
"x-order": 5
},
"encrypted": {
"description": "This field is set to true if the dump was created with encryption enabled, and false otherwise.",
"type": "boolean",
"x-order": 6
}
}
},
Expand Down Expand Up @@ -5007,6 +5012,16 @@
"ignore_load": {
"type": "boolean",
"x-order": 4
},
"enable_encryption": {
"description": "If true, the dump will be encrypted. Note that enabling encryption may increase the time required to create the dump.",
"type": "boolean",
"x-order": 5
},
"encryption_password": {
"description": "The password used for encryption. It must be at least 8 characters long. This field is required if enable_encryption is true.",
"type": "string",
"x-order": 6
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions build/packages/rpm/server/SPECS/pmm-dump.spec
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

%global repo pmm-dump
%global provider github.com/percona/%{repo}
%global commit 4c38e9442fb2f6b0146cd5a581f18db4ebb034f7
%global commit 8353b46afd09746c07a6d1c001dd1ef72e6c4761
%global shortcommit %(c=%{commit}; echo ${c:0:7})
%define build_timestamp %(date -u +"%y%m%d%H%M")
%define release 1
%define rpm_release %{release}.%{build_timestamp}.%{shortcommit}%{?dist}

Name: pmm-dump
Version: 0.7.1-ga
Version: 0.8.0-ga
Release: %{rpm_release}
Summary: Percona PMM Dump allows to export and import monitoring metrics and query analytics.

Expand Down Expand Up @@ -37,6 +37,9 @@ install -p -m 0755 pmm-dump %{buildroot}%{_sbindir}/pmm-dump


%changelog
* Wed Apr 22 2026 Maxim Kondratenko <maxim.kondratenko@percona.com> - 3.7.1
- PMM-14441 Upgrade pmm-dump to support encryption

* Wed Sep 24 2025 Michael Okoko <michael.okoko@percona.com> - 3.4.1
- PMM-14349 Update pmm-dump sources.

Expand Down
21 changes: 13 additions & 8 deletions managed/models/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,10 @@ var databaseSchema = [][]string{
117: {
`DROP TABLE IF EXISTS percona_sso_details`,
},
118: {
`ALTER TABLE dumps ADD COLUMN encrypted boolean NOT NULL DEFAULT false`,
`UPDATE dumps SET encrypted = false`,
},
}

// ^^^ Avoid default values in schema definition. ^^^
Expand Down Expand Up @@ -1272,7 +1276,8 @@ func SetupDB(ctx context.Context, sqlDB *sql.DB, params SetupDBParams) (*reform.
if params.HANodeID != "" {
return nil, fmt.Errorf("cannot auto-provision database in HA mode: %w", errCV)
}
if err := initWithRoot(params); err != nil {
err := initWithRoot(ctx, params)
if err != nil {
return nil, err
}
errCV = checkVersion(ctx, db)
Expand Down Expand Up @@ -1374,7 +1379,7 @@ func checkVersion(ctx context.Context, db reform.DBTXContext) error {
}

// initWithRoot tries to create the user and the database.
func initWithRoot(params SetupDBParams) error {
func initWithRoot(ctx context.Context, params SetupDBParams) error {
if params.Logf != nil {
params.Logf("Creating database %s and role %s", params.Name, params.Username)
}
Expand All @@ -1394,31 +1399,31 @@ func initWithRoot(params SetupDBParams) error {
defer db.Close() //nolint:errcheck

var countDatabases int
err = db.QueryRow(`SELECT COUNT(*) FROM pg_database WHERE datname = $1`, params.Name).Scan(&countDatabases)
err = db.QueryRowContext(ctx, `SELECT COUNT(*) FROM pg_database WHERE datname = $1`, params.Name).Scan(&countDatabases)
Comment thread
maxkondr marked this conversation as resolved.
Outdated
if err != nil {
return fmt.Errorf("failed to select records from the database: %w", err)
}

if countDatabases == 0 {
_, err = db.Exec(fmt.Sprintf(`CREATE DATABASE "%s"`, params.Name))
_, err = db.ExecContext(ctx, fmt.Sprintf(`CREATE DATABASE "%s"`, params.Name))
if err != nil {
return fmt.Errorf("failed to create database %s: %w", params.Name, err)
}
}

var countRoles int
err = db.QueryRow(`SELECT COUNT(*) FROM pg_roles WHERE rolname=$1`, params.Username).Scan(&countRoles)
err = db.QueryRowContext(ctx, `SELECT COUNT(*) FROM pg_roles WHERE rolname=$1`, params.Username).Scan(&countRoles)
if err != nil {
return fmt.Errorf("failed to select records from the database: %w", err)
}

if countRoles == 0 {
_, err = db.Exec(fmt.Sprintf(`CREATE USER "%s" LOGIN PASSWORD '%s'`, params.Username, params.Password))
_, err = db.ExecContext(ctx, fmt.Sprintf(`CREATE USER "%s" LOGIN PASSWORD '%s'`, params.Username, params.Password))
if err != nil {
return fmt.Errorf("failed to create user %s: %w", params.Username, err)
}

_, err = db.Exec(`GRANT ALL PRIVILEGES ON DATABASE $1 TO $2`, params.Name, params.Username)
_, err = db.ExecContext(ctx, `GRANT ALL PRIVILEGES ON DATABASE $1 TO $2`, params.Name, params.Username)
if err != nil {
return fmt.Errorf("failed to grant privileges to user %s on database %s: %w", params.Username, params.Name, err)
}
Expand All @@ -1427,7 +1432,7 @@ func initWithRoot(params SetupDBParams) error {
// scram-sha-256 during an upgrade, leaving the role with no usable password hash).
// initWithRoot is only ever called after a 28000/28P01 auth error, so resetting the
// password to the currently configured value is OK.
_, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" WITH PASSWORD '%s'`, params.Username, params.Password))
_, err = db.ExecContext(ctx, fmt.Sprintf(`ALTER USER "%s" WITH PASSWORD '%s'`, params.Username, params.Password))
if err != nil {
return fmt.Errorf("failed to update password for user %s: %w", params.Username, err)
}
Expand Down
1 change: 1 addition & 0 deletions managed/models/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ type Dump struct {
IgnoreLoad bool `reform:"ignore_load"`
CreatedAt time.Time `reform:"created_at"`
UpdatedAt time.Time `reform:"updated_at"`
Encrypted bool `reform:"encrypted"`
}

// BeforeInsert implements reform.BeforeInserter interface.
Expand Down
2 changes: 2 additions & 0 deletions managed/models/dump_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type CreateDumpParams struct {
EndTime *time.Time
ExportQAN bool
IgnoreLoad bool
Encrypted bool
}

// Validate checks the validity of CreateDumpParams.
Expand Down Expand Up @@ -87,6 +88,7 @@ func CreateDump(q *reform.Querier, params CreateDumpParams) (*Dump, error) {
EndTime: params.EndTime,
ExportQAN: params.ExportQAN,
IgnoreLoad: params.IgnoreLoad,
Encrypted: params.Encrypted,
}
if err := q.Insert(dump); err != nil {
return nil, errors.WithStack(err)
Expand Down
26 changes: 25 additions & 1 deletion managed/models/dump_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestDumps(t *testing.T) {
})

t.Run("create", func(t *testing.T) {
t.Run("normal", func(t *testing.T) {
t.Run("normal non-encrypted", func(t *testing.T) {
endTime := time.Now()
startTime := endTime.Add(-10 * time.Minute)

Expand All @@ -62,6 +62,30 @@ func TestDumps(t *testing.T) {
assert.Equal(t, createDumpParams.IgnoreLoad, dump.IgnoreLoad)
})

t.Run("normal encrypted", func(t *testing.T) {
endTime := time.Now()
startTime := endTime.Add(-10 * time.Minute)

createDumpParams := models.CreateDumpParams{
ServiceNames: []string{"foo", "bar"},
StartTime: &startTime,
EndTime: &endTime,
ExportQAN: false,
IgnoreLoad: true,
Encrypted: true,
}
dump, err := models.CreateDump(tx.Querier, createDumpParams)
require.NoError(t, err)
assert.NotEmpty(t, dump.ID)
assert.Equal(t, models.DumpStatusInProgress, dump.Status)
assert.ElementsMatch(t, createDumpParams.ServiceNames, dump.ServiceNames)
assert.Equal(t, createDumpParams.StartTime, dump.StartTime)
assert.Equal(t, createDumpParams.EndTime, dump.EndTime)
assert.Equal(t, createDumpParams.ExportQAN, dump.ExportQAN)
assert.Equal(t, createDumpParams.IgnoreLoad, dump.IgnoreLoad)
assert.Equal(t, createDumpParams.Encrypted, dump.Encrypted)
})

t.Run("invalid start and end time", func(t *testing.T) {
endTime := time.Now()
startTime := endTime.Add(10 * time.Minute)
Expand Down
Loading
Loading