From 6bbf7bbb9fb0007a731f44025f5cb4caab1251b1 Mon Sep 17 00:00:00 2001 From: Ben Schumacher Date: Thu, 4 Jul 2024 21:56:38 +0200 Subject: [PATCH] Update Packet metadata generation based on feedback (#27490) --- server/channels/app/plugin_api.go | 9 - server/public/model/metadata.go | 122 ------------ server/public/model/metadata_test.go | 176 ----------------- server/public/model/packet_metadata.go | 131 +++++++++++++ server/public/model/packet_metadata_test.go | 185 ++++++++++++++++++ server/public/plugin/api.go | 7 - .../plugin/api_timer_layer_generated.go | 7 - server/public/plugin/client_rpc_generated.go | 30 --- server/public/plugin/plugintest/api.go | 30 --- server/public/pluginapi/system.go | 58 ++++++ server/public/pluginapi/system_test.go | 88 +++++++-- 11 files changed, 446 insertions(+), 397 deletions(-) delete mode 100644 server/public/model/metadata.go delete mode 100644 server/public/model/metadata_test.go create mode 100644 server/public/model/packet_metadata.go create mode 100644 server/public/model/packet_metadata_test.go diff --git a/server/channels/app/plugin_api.go b/server/channels/app/plugin_api.go index 801153d571..8daf00e4e7 100644 --- a/server/channels/app/plugin_api.go +++ b/server/channels/app/plugin_api.go @@ -1343,12 +1343,3 @@ func (api *PluginAPI) InviteRemoteToChannel(channelID string, remoteID, userID s func (api *PluginAPI) UninviteRemoteFromChannel(channelID string, remoteID string) error { return api.app.UninviteRemoteFromChannel(channelID, remoteID) } - -func (api *PluginAPI) GenerateSupportMetadata(pluginMeta map[string]any) (*model.Metadata, error) { - md, err := model.GeneratePluginMetadata(api.manifest, api.GetLicense(), api.GetTelemetryId(), pluginMeta) - if err != nil { - return nil, err - } - - return md, nil -} diff --git a/server/public/model/metadata.go b/server/public/model/metadata.go deleted file mode 100644 index a0487c018d..0000000000 --- a/server/public/model/metadata.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package model - -import ( - "encoding/json" - "fmt" - - "github.com/blang/semver/v4" -) - -type MetadataType string - -const ( - CurrentMetadataVersion int = 1 - ServerMetadata MetadataType = "mattermost-support-package" - PluginMetadata MetadataType = "plugin-support-package" -) - -type Metadata struct { - // Required Fields - Version int `json:"version"` - Type MetadataType `json:"type"` - GeneratedAt int64 `json:"generated_at"` - ServerVersion string `json:"server_version"` - ServerID string `json:"server_id"` - LicenseID string `json:"license_id"` - CustomerID string `json:"customer_id"` - // Optional Fields - Extras map[string]any `json:"extras"` -} - -func (md *Metadata) Validate() error { - if md.Version < 1 { - return fmt.Errorf("metadata version should be greater than 1") - } - - if md.Type != ServerMetadata && md.Type != PluginMetadata { - return fmt.Errorf("unrecognized metadata type: %s", md.Type) - } - - if _, err := semver.ParseTolerant(md.ServerVersion); err != nil { - return fmt.Errorf("could not parse server version: %w", err) - } - - if !IsValidId(md.ServerID) { - return fmt.Errorf("server id is not a valid id %q", md.ServerID) - } - - if !IsValidId(md.LicenseID) && md.LicenseID != "" { - return fmt.Errorf("license id is not a valid id %q", md.LicenseID) - } - - if !IsValidId(md.CustomerID) && md.CustomerID != "" { - return fmt.Errorf("customer id is not a valid id %q", md.CustomerID) - } - - return nil -} - -func ParseMetadata(b []byte) (*Metadata, error) { - v := struct { - Version int `json:"version"` - }{} - - err := json.Unmarshal(b, &v) - if err != nil { - return nil, err - } - - switch v.Version { - case 1: - var md Metadata - err = json.Unmarshal(b, &md) - if err != nil { - return nil, err - } - - err = md.Validate() - if err != nil { - return nil, err - } - - return &md, nil - default: - return nil, fmt.Errorf("unsupported metadata version: %d", v.Version) - } -} - -// GeneratePluginMetadata is a utility function to generate a plugin metadata for support packets. -// It will construct it from the manifest, license and so on. The plugin_id and plugin_version will be -// used from the manifest. -func GeneratePluginMetadata(manifest *Manifest, license *License, serverID string, pluginMeta map[string]any) (*Metadata, error) { - if pluginMeta == nil { - pluginMeta = make(map[string]any) - } - - // we override the plugin_id and version fields from the manifest - pluginMeta["plugin_id"] = manifest.Id - pluginMeta["plugin_version"] = manifest.Version - - md := Metadata{ - Version: CurrentMetadataVersion, - Type: PluginMetadata, - GeneratedAt: GetMillis(), - ServerVersion: CurrentVersion, - ServerID: serverID, - Extras: pluginMeta, - } - - if license != nil { - md.LicenseID = license.Id - md.CustomerID = license.Customer.Id - } - - if err := md.Validate(); err != nil { - return nil, fmt.Errorf("invalid metadata: %w", err) - } - - return &md, nil -} diff --git a/server/public/model/metadata_test.go b/server/public/model/metadata_test.go deleted file mode 100644 index 0e78c0a1d3..0000000000 --- a/server/public/model/metadata_test.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package model - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestValidate(t *testing.T) { - tests := []struct { - name string - metadata Metadata - expectErr bool - }{ - { - name: "Valid Metadata", - metadata: Metadata{ - Version: 1, - Type: ServerMetadata, - GeneratedAt: 1622569200, - ServerVersion: "5.33.3", - ServerID: NewId(), - LicenseID: NewId(), - CustomerID: NewId(), - Extras: map[string]interface{}{"key": "value"}, - }, - expectErr: false, - }, - { - name: "Valid Metadata without license", - metadata: Metadata{ - Version: 1, - Type: ServerMetadata, - GeneratedAt: 1622569200, - ServerVersion: "5.33.3", - ServerID: NewId(), - Extras: map[string]interface{}{"key": "value"}, - }, - expectErr: false, - }, - { - name: "Invalid Version", - metadata: Metadata{ - Version: 0, - Type: ServerMetadata, - GeneratedAt: 1622569200, - ServerVersion: "5.33.3", - ServerID: NewId(), - LicenseID: NewId(), - CustomerID: NewId(), - }, - expectErr: true, - }, - { - name: "Invalid Type", - metadata: Metadata{ - Version: 1, - Type: "invalid-type", - GeneratedAt: 1622569200, - ServerVersion: "5.33.3", - ServerID: NewId(), - LicenseID: NewId(), - CustomerID: NewId(), - }, - expectErr: true, - }, - { - name: "Invalid Server Version", - metadata: Metadata{ - Version: 1, - Type: ServerMetadata, - GeneratedAt: 1622569200, - ServerVersion: "invalid-version", - ServerID: "valid-server-id", - LicenseID: "valid-license-id", - CustomerID: "valid-customer-id", - }, - expectErr: true, - }, - { - name: "Invalid Server ID", - metadata: Metadata{ - Version: 1, - Type: ServerMetadata, - GeneratedAt: 1622569200, - ServerVersion: "5.33.3", - ServerID: "", - LicenseID: NewId(), - CustomerID: NewId(), - }, - expectErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.metadata.Validate() - if tt.expectErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestParseMetadata(t *testing.T) { - validMetadataJSON := `{ - "version": 1, - "type": "mattermost-support-package", - "generated_at": 1622569200, - "server_version": "5.33.3", - "server_id": "8fqk9rti13fmpxdd5934a3xsxh", - "license_id": "3g3pqn8in3brzjkozcn1kdidgr", - "customer_id": "74cmws7gf3ykpj31car7zahsny", - "extras": {"key": "value"} - }` - - invalidVersionJSON := `{ - "version": 0, - "type": "mattermost-support-package", - "generated_at": 1622569200, - "server_version": "5.33.3", - "server_id": "8fqk9rti13fmpxdd5934a3xsxh", - "license_id": "3g3pqn8in3brzjkozcn1kdidgr", - "customer_id": "74cmws7gf3ykpj31car7zahsny", - }` - - unsupportedVersionJSON := `{ - "version": 2, - "type": "mattermost-support-package", - "generated_at": 1622569200, - "server_version": "5.33.3", - "server_id": "8fqk9rti13fmpxdd5934a3xsxh", - "license_id": "3g3pqn8in3brzjkozcn1kdidgr", - "customer_id": "74cmws7gf3ykpj31car7zahsny", - }` - - tests := []struct { - name string - jsonData string - expectErr bool - }{ - { - name: "Valid Metadata JSON", - jsonData: validMetadataJSON, - expectErr: false, - }, - { - name: "Invalid Version in JSON", - jsonData: invalidVersionJSON, - expectErr: true, - }, - { - name: "Unsupported Version in JSON", - jsonData: unsupportedVersionJSON, - expectErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - md, err := ParseMetadata([]byte(tt.jsonData)) - if tt.expectErr { - require.Error(t, err) - require.Nil(t, md) - } else { - require.NoError(t, err) - require.NotNil(t, md) - } - }) - } -} diff --git a/server/public/model/packet_metadata.go b/server/public/model/packet_metadata.go new file mode 100644 index 0000000000..d3a68dfd2b --- /dev/null +++ b/server/public/model/packet_metadata.go @@ -0,0 +1,131 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package model + +import ( + "fmt" + + "github.com/blang/semver/v4" + "gopkg.in/yaml.v2" +) + +type PacketType string + +const ( + CurrentMetadataVersion int = 1 + SupportPacketType PacketType = "support-packet" + PluginPacketType PacketType = "plugin-packet" + + PacketMetadataFileName = "metadata.yaml" +) + +// PacketMetadata contains information about the server and the configured license (if there is one), +// It's used in file archives, so called Packets, that customer send to Mattermost Staff for review. +// For example, this metadata is attached to the Support Packet and the Metrics plugin Packet. +type PacketMetadata struct { + // Required Fields + + Version int `yaml:"version"` + Type PacketType `yaml:"type"` + GeneratedAt int64 `yaml:"generated_at"` + ServerVersion string `yaml:"server_version"` + ServerID string `yaml:"server_id"` + + // Optional Fields + + LicenseID string `yaml:"license_id"` + CustomerID string `yaml:"customer_id"` + Extras map[string]any `yaml:"extras,omitempty"` +} + +func (md *PacketMetadata) Validate() error { + if md.Version < 1 { + return fmt.Errorf("metadata version should be greater than 1") + } + + switch md.Type { + case SupportPacketType, PluginPacketType: + default: + return fmt.Errorf("unrecognized packet type: %s", md.Type) + } + + if md.GeneratedAt <= 0 { + return fmt.Errorf("generated_at should be a positive number") + } + + if _, err := semver.ParseTolerant(md.ServerVersion); err != nil { + return fmt.Errorf("could not parse server version: %w", err) + } + + if !IsValidId(md.ServerID) { + return fmt.Errorf("server id is not a valid id %q", md.ServerID) + } + + if md.LicenseID != "" && !IsValidId(md.LicenseID) { + return fmt.Errorf("license id is not a valid id %q", md.LicenseID) + } + + if md.CustomerID != "" && !IsValidId(md.CustomerID) { + return fmt.Errorf("customer id is not a valid id %q", md.CustomerID) + } + + return nil +} + +func ParsePacketMetadata(b []byte) (*PacketMetadata, error) { + v := struct { + Version int `yaml:"version"` + }{} + + err := yaml.Unmarshal(b, &v) + if err != nil { + return nil, err + } + + switch v.Version { + case 1: + var md PacketMetadata + err = yaml.Unmarshal(b, &md) + if err != nil { + return nil, err + } + + err = md.Validate() + if err != nil { + return nil, err + } + + return &md, nil + default: + return nil, fmt.Errorf("unsupported metadata version: %d", v.Version) + } +} + +// GeneratePacketMetadata is a utility function to generate metadata for customer provided Packets. +// It will construct it from serverID and optionally a license. +func GeneratePacketMetadata(serverID string, license *License, extra map[string]any) (*PacketMetadata, error) { + if extra == nil { + extra = make(map[string]any) + } + + md := PacketMetadata{ + Version: CurrentMetadataVersion, + Type: PluginPacketType, + GeneratedAt: GetMillis(), + ServerVersion: CurrentVersion, + ServerID: serverID, + Extras: extra, + } + + if license != nil { + md.LicenseID = license.Id + md.CustomerID = license.Customer.Id + } + + if err := md.Validate(); err != nil { + return nil, fmt.Errorf("invalid metadata: %w", err) + } + + return &md, nil +} diff --git a/server/public/model/packet_metadata_test.go b/server/public/model/packet_metadata_test.go new file mode 100644 index 0000000000..2f02eed66d --- /dev/null +++ b/server/public/model/packet_metadata_test.go @@ -0,0 +1,185 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package model + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" +) + +func TestPacketMetadataValidate(t *testing.T) { + tests := map[string]struct { + name string + metadata PacketMetadata + expectErr bool + }{ + "Valid Metadata": { + metadata: PacketMetadata{ + Version: 1, + Type: SupportPacketType, + GeneratedAt: 1720097114454, + ServerVersion: "5.33.3", + ServerID: NewId(), + LicenseID: NewId(), + CustomerID: NewId(), + Extras: map[string]interface{}{"key": "value"}, + }, + expectErr: false, + }, + "Valid Metadata without license": { + metadata: PacketMetadata{ + Version: 1, + Type: SupportPacketType, + GeneratedAt: 1720097114454, + ServerVersion: "5.33.3", + ServerID: NewId(), + Extras: map[string]interface{}{"key": "value"}, + }, + expectErr: false, + }, + "Invalid Version": { + metadata: PacketMetadata{ + Version: 0, + Type: SupportPacketType, + GeneratedAt: 1720097114454, + ServerVersion: "5.33.3", + ServerID: NewId(), + LicenseID: NewId(), + CustomerID: NewId(), + }, + expectErr: true, + }, + "Invalid Type": { + metadata: PacketMetadata{ + Version: 1, + Type: "invalid-type", + GeneratedAt: 1720097114454, + ServerVersion: "5.33.3", + ServerID: NewId(), + LicenseID: NewId(), + CustomerID: NewId(), + }, + expectErr: true, + }, + "Invalid GeneratedAt": { + metadata: PacketMetadata{ + Version: 1, + Type: SupportPacketType, + GeneratedAt: 0, + ServerVersion: "5.33.3", + ServerID: NewId(), + LicenseID: NewId(), + CustomerID: NewId(), + }, + expectErr: true, + }, + "Invalid Server Version": { + metadata: PacketMetadata{ + Version: 1, + Type: SupportPacketType, + GeneratedAt: 1720097114454, + ServerVersion: "invalid-version", + ServerID: "valid-server-id", + LicenseID: "valid-license-id", + CustomerID: "valid-customer-id", + }, + expectErr: true, + }, + "Invalid Server ID": { + metadata: PacketMetadata{ + Version: 1, + Type: SupportPacketType, + GeneratedAt: 1720097114454, + ServerVersion: "5.33.3", + ServerID: "", + LicenseID: NewId(), + CustomerID: NewId(), + }, + expectErr: true, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + err := tt.metadata.Validate() + if tt.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestParsePacketMetadata(t *testing.T) { + valid := ` +version: 1 +type: support-packet +generated_at: 1622569200 +server_version: 5.33.3 +server_id: 8fqk9rti13fmpxdd5934a3xsxh +license_id: 3g3pqn8in3brzjkozcn1kdidgr +customer_id: 74cmws7gf3ykpj31car7zahsny +extras: + key: value +` + + invalidVersion := ` +version: 0 +type: support-packet +generated_at: 1622569200 +server_version: 5.33.3 +server_id: 8fqk9rti13fmpxdd5934a3xsxh +license_id: 3g3pqn8in3brzjkozcn1kdidgr +customer_id: 74cmws7gf3ykpj31car7zahsny +` + + unsupportedVersion := ` +version: 2 +type: support-packet +generated_at: 1622569200 +server_version: 5.33.3 +server_id: 8fqk9rti13fmpxdd5934a3xsxh +license_id: 3g3pqn8in3brzjkozcn1kdidgr +customer_id: 74cmws7gf3ykpj31car7zahsny +` + + tests := map[string]struct { + yamlData string + expectErr bool + }{ + "Valid Metadata YAML": { + yamlData: valid, + expectErr: false, + }, + "Invalid Version in YAML": { + yamlData: invalidVersion, + expectErr: true, + }, + "Unsupported Version in YAML": { + yamlData: unsupportedVersion, + expectErr: true, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + // Confirm valid yaml + var md *PacketMetadata + err := yaml.Unmarshal([]byte(tt.yamlData), &md) + require.NoError(t, err) + + md, err = ParsePacketMetadata([]byte(tt.yamlData)) + if tt.expectErr { + require.Error(t, err) + require.Nil(t, md) + } else { + require.NoError(t, err) + require.NotNil(t, md) + } + }) + } +} diff --git a/server/public/plugin/api.go b/server/public/plugin/api.go index 173c878099..bc540024d8 100644 --- a/server/public/plugin/api.go +++ b/server/public/plugin/api.go @@ -1306,13 +1306,6 @@ type API interface { // @tag User // Minimum server version: 9.8 UpdateUserRoles(userID, newRoles string) (*model.User, *model.AppError) - - // GenerateSupportMetadata generates the metadata for a support packet. - // pluginMeta should contain the values that plugin wants to insert into a standard support packet metadata. - // - // @tag Metadata - // Minimum server version: 9.10 - GenerateSupportMetadata(pluginMeta map[string]any) (*model.Metadata, error) } var handshake = plugin.HandshakeConfig{ diff --git a/server/public/plugin/api_timer_layer_generated.go b/server/public/plugin/api_timer_layer_generated.go index 11cfcd9769..2643adb8a0 100644 --- a/server/public/plugin/api_timer_layer_generated.go +++ b/server/public/plugin/api_timer_layer_generated.go @@ -1378,10 +1378,3 @@ func (api *apiTimerLayer) UpdateUserRoles(userID, newRoles string) (*model.User, api.recordTime(startTime, "UpdateUserRoles", _returnsB == nil) return _returnsA, _returnsB } - -func (api *apiTimerLayer) GenerateSupportMetadata(pluginMeta map[string]any) (*model.Metadata, error) { - startTime := timePkg.Now() - _returnsA, _returnsB := api.apiImpl.GenerateSupportMetadata(pluginMeta) - api.recordTime(startTime, "GenerateSupportMetadata", _returnsB == nil) - return _returnsA, _returnsB -} diff --git a/server/public/plugin/client_rpc_generated.go b/server/public/plugin/client_rpc_generated.go index 0591d5f70f..a2fa59c9de 100644 --- a/server/public/plugin/client_rpc_generated.go +++ b/server/public/plugin/client_rpc_generated.go @@ -6630,33 +6630,3 @@ func (s *apiRPCServer) UpdateUserRoles(args *Z_UpdateUserRolesArgs, returns *Z_U } return nil } - -type Z_GenerateSupportMetadataArgs struct { - A map[string]any -} - -type Z_GenerateSupportMetadataReturns struct { - A *model.Metadata - B error -} - -func (g *apiRPCClient) GenerateSupportMetadata(pluginMeta map[string]any) (*model.Metadata, error) { - _args := &Z_GenerateSupportMetadataArgs{pluginMeta} - _returns := &Z_GenerateSupportMetadataReturns{} - if err := g.client.Call("Plugin.GenerateSupportMetadata", _args, _returns); err != nil { - log.Printf("RPC call to GenerateSupportMetadata API failed: %s", err.Error()) - } - return _returns.A, _returns.B -} - -func (s *apiRPCServer) GenerateSupportMetadata(args *Z_GenerateSupportMetadataArgs, returns *Z_GenerateSupportMetadataReturns) error { - if hook, ok := s.impl.(interface { - GenerateSupportMetadata(pluginMeta map[string]any) (*model.Metadata, error) - }); ok { - returns.A, returns.B = hook.GenerateSupportMetadata(args.A) - returns.B = encodableError(returns.B) - } else { - return encodableError(fmt.Errorf("API GenerateSupportMetadata called but not implemented.")) - } - return nil -} diff --git a/server/public/plugin/plugintest/api.go b/server/public/plugin/plugintest/api.go index 91077eafdb..07f212fdae 100644 --- a/server/public/plugin/plugintest/api.go +++ b/server/public/plugin/plugintest/api.go @@ -891,36 +891,6 @@ func (_m *API) ExtendSessionExpiry(sessionID string, newExpiry int64) *model.App return r0 } -// GenerateSupportMetadata provides a mock function with given fields: pluginMeta -func (_m *API) GenerateSupportMetadata(pluginMeta map[string]interface{}) (*model.Metadata, error) { - ret := _m.Called(pluginMeta) - - if len(ret) == 0 { - panic("no return value specified for GenerateSupportMetadata") - } - - var r0 *model.Metadata - var r1 error - if rf, ok := ret.Get(0).(func(map[string]interface{}) (*model.Metadata, error)); ok { - return rf(pluginMeta) - } - if rf, ok := ret.Get(0).(func(map[string]interface{}) *model.Metadata); ok { - r0 = rf(pluginMeta) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.Metadata) - } - } - - if rf, ok := ret.Get(1).(func(map[string]interface{}) error); ok { - r1 = rf(pluginMeta) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetBot provides a mock function with given fields: botUserId, includeDeleted func (_m *API) GetBot(botUserId string, includeDeleted bool) (*model.Bot, *model.AppError) { ret := _m.Called(botUserId, includeDeleted) diff --git a/server/public/pluginapi/system.go b/server/public/pluginapi/system.go index e548d5d8a8..786e361848 100644 --- a/server/public/pluginapi/system.go +++ b/server/public/pluginapi/system.go @@ -2,11 +2,14 @@ package pluginapi import ( "net/url" + "os" "path" + filePath "path" "time" "github.com/blang/semver/v4" "github.com/pkg/errors" + "gopkg.in/yaml.v2" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" @@ -102,11 +105,20 @@ func (s *SystemService) GetSystemInstallDate() (time.Time, error) { // GetDiagnosticID returns a unique identifier used by the server for diagnostic reports. // // Minimum server version: 5.10 +// +// Deprecated: Use GetTelemetryID instead. It returns the same value. func (s *SystemService) GetDiagnosticID() string { // TODO: Consider deprecating/rewriting in favor of just using GetUnsanitizedConfig(). return s.api.GetDiagnosticId() } +// GetTelemetryID returns a unique identifier used by the server for telemetry reports. +// +// Minimum server version: 5.10 +func (s *SystemService) GetTelemetryID() string { + return s.api.GetTelemetryId() +} + // RequestTrialLicense requests a trial license and installs it in the server. // If the server version is lower than 5.36.0, an error is returned. // @@ -122,3 +134,49 @@ func (s *SystemService) RequestTrialLicense(requesterID string, users int, terms err := s.api.RequestTrialLicense(requesterID, users, termsAccepted, receiveEmailsAccepted) return normalizeAppErr(err) } + +// GeneratePacketMetadata generates metadata for Customer Packets, encods it to YAML and saves it to a file +// defined by the path parameter. +// pluginMeta should contain the values that plugin wants to insert into the standard metadata. +// +// The plugin_id and plugin_version will be used from the manifest. +// If pluginMeta contains plugin_id or plugin_version, they will be overridden. +// +// It returns the path to the file where the metadata was saved. +// +// @tag Metadata +// Minimum server version: 5.10 +func (s *SystemService) GeneratePacketMetadata(path string, pluginMeta map[string]any) (string, error) { + manifest, err := s.GetManifest() + if err != nil { + return "", errors.Wrap(err, "failed to get manifest") + } + license := s.GetLicense() + serverID := s.GetTelemetryID() + + if pluginMeta == nil { + pluginMeta = make(map[string]any) + } + + // we override the plugin_id and version fields from the manifest + pluginMeta["plugin_id"] = manifest.Id + pluginMeta["plugin_version"] = manifest.Version + + md, err := model.GeneratePacketMetadata(serverID, license, pluginMeta) + if err != nil { + return "", errors.Wrap(err, "failed to get packet metadata") + } + filePath := filePath.Join(path, model.PacketMetadataFileName) + f, err := os.Create(filePath) + if err != nil { + return "", errors.Wrap(err, "failed to create packet metadata file") + } + defer f.Close() + + err = yaml.NewEncoder(f).Encode(md) + if err != nil { + return "", errors.Wrap(err, "failed to create packet metadata file") + } + + return filePath, nil +} diff --git a/server/public/pluginapi/system_test.go b/server/public/pluginapi/system_test.go index 5597365f71..cd0936ada3 100644 --- a/server/public/pluginapi/system_test.go +++ b/server/public/pluginapi/system_test.go @@ -1,12 +1,14 @@ package pluginapi_test import ( + "encoding/json" "os" "path/filepath" "testing" "github.com/pkg/errors" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin/plugintest" @@ -15,25 +17,13 @@ import ( func TestGetManifest(t *testing.T) { t.Run("valid manifest", func(t *testing.T) { - content := []byte(` - { - "id": "some.id", - "name": "Some Name" - } - `) expectedManifest := &model.Manifest{ - Id: "some.id", - Name: "Some Name", + Id: "some.id", + Name: "Some Name", + Version: "1.0.0", } - dir, err := os.MkdirTemp("", "") - require.NoError(t, err) - defer os.RemoveAll(dir) - - tmpfn := filepath.Join(dir, "plugin.json") - //nolint:gosec //only used in tests - err = os.WriteFile(tmpfn, content, 0o666) - require.NoError(t, err) + dir := generateManifest(t) api := &plugintest.API{} api.On("GetBundlePath").Return(dir, nil) @@ -102,3 +92,69 @@ func TestRequestTrialLicense(t *testing.T) { require.NoError(t, err) }) } + +func TestGenerateCustomerPacketMetadata(t *testing.T) { + licenseID := model.NewId() + customerID := model.NewId() + telemetryID := model.NewId() + t.Run("happy path", func(t *testing.T) { + api := plugintest.NewAPI(t) + client := pluginapi.NewClient(api, &plugintest.Driver{}) + + dir := generateManifest(t) + api.On("GetBundlePath").Return(dir, nil) + api.On("GetLicense").Return(&model.License{ + Id: licenseID, + Customer: &model.Customer{ + Id: customerID, + }, + }) + api.On("GetTelemetryId").Return(telemetryID) + + path := os.TempDir() + filePath, err := client.System.GeneratePacketMetadata(path, nil) + require.NoError(t, err) + + f, err := os.Open(filePath) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, f.Close()) + }) + + var md model.PacketMetadata + err = yaml.NewDecoder(f).Decode(&md) + require.NoError(t, err) + + require.Equal(t, model.CurrentMetadataVersion, md.Version) + require.Equal(t, model.PluginPacketType, md.Type) + require.NotZero(t, md.GeneratedAt) + require.Equal(t, model.CurrentVersion, md.ServerVersion) + require.Equal(t, telemetryID, md.ServerID) + require.Equal(t, licenseID, md.LicenseID) + require.Equal(t, customerID, md.CustomerID) + require.Equal(t, "some.id", md.Extras["plugin_id"]) + require.Equal(t, "1.0.0", md.Extras["plugin_version"]) + }) +} + +func generateManifest(t *testing.T) string { + manifest := &model.Manifest{ + Id: "some.id", + Name: "Some Name", + Version: "1.0.0", + } + + dir, err := os.MkdirTemp("", "") + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, os.RemoveAll(dir)) + }) + + tmpfn := filepath.Join(dir, "plugin.json") + f, err := os.Create(tmpfn) + require.NoError(t, err) + err = json.NewEncoder(f).Encode(manifest) + require.NoError(t, err) + + return dir +}