1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-19 21:39:40 +02:00

feat(snapshots): avoid parsing raw snapshots when possible BE-11724 (#560)

This commit is contained in:
andres-portainer 2025-03-24 19:33:05 -03:00 committed by GitHub
parent 0dfde1374d
commit 995c3ef81b
16 changed files with 89 additions and 28 deletions

View file

@ -159,6 +159,7 @@ type (
SnapshotService interface { SnapshotService interface {
BaseCRUD[portainer.Snapshot, portainer.EndpointID] BaseCRUD[portainer.Snapshot, portainer.EndpointID]
ReadWithoutSnapshotRaw(ID portainer.EndpointID) (*portainer.Snapshot, error)
} }
// SSLSettingsService represents a service for managing application settings // SSLSettingsService represents a service for managing application settings

View file

@ -38,3 +38,16 @@ func (service *Service) Tx(tx portainer.Transaction) ServiceTx {
func (service *Service) Create(snapshot *portainer.Snapshot) error { func (service *Service) Create(snapshot *portainer.Snapshot) error {
return service.Connection.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot) return service.Connection.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot)
} }
func (service *Service) ReadWithoutSnapshotRaw(ID portainer.EndpointID) (*portainer.Snapshot, error) {
var snapshot *portainer.Snapshot
err := service.Connection.ViewTx(func(tx portainer.Transaction) error {
var err error
snapshot, err = service.Tx(tx).ReadWithoutSnapshotRaw(ID)
return err
})
return snapshot, err
}

View file

@ -12,3 +12,26 @@ type ServiceTx struct {
func (service ServiceTx) Create(snapshot *portainer.Snapshot) error { func (service ServiceTx) Create(snapshot *portainer.Snapshot) error {
return service.Tx.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot) return service.Tx.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot)
} }
func (service ServiceTx) ReadWithoutSnapshotRaw(ID portainer.EndpointID) (*portainer.Snapshot, error) {
var snapshot struct {
Docker *struct {
X struct{} `json:"DockerSnapshotRaw"`
*portainer.DockerSnapshot
} `json:"Docker"`
portainer.Snapshot
}
identifier := service.Connection.ConvertToKey(int(ID))
if err := service.Tx.GetObject(service.Bucket, identifier, &snapshot); err != nil {
return nil, err
}
if snapshot.Docker != nil {
snapshot.Snapshot.Docker = snapshot.Docker.DockerSnapshot
}
return &snapshot.Snapshot, nil
}

View file

@ -19,6 +19,8 @@ import (
// @security jwt // @security jwt
// @produce json // @produce json
// @param id path int true "Environment(Endpoint) identifier" // @param id path int true "Environment(Endpoint) identifier"
// @param excludeSnapshot query bool false "if true, the snapshot data won't be retrieved"
// @param excludeSnapshotRaw query bool false "if true, the SnapshotRaw field won't be retrieved"
// @success 200 {object} portainer.Endpoint "Success" // @success 200 {object} portainer.Endpoint "Success"
// @failure 400 "Invalid request" // @failure 400 "Invalid request"
// @failure 404 "Environment(Endpoint) not found" // @failure 404 "Environment(Endpoint) not found"
@ -37,8 +39,7 @@ func (handler *Handler) endpointInspect(w http.ResponseWriter, r *http.Request)
return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err) return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err)
} }
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err := handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint); err != nil {
if err != nil {
return httperror.Forbidden("Permission denied to access environment", err) return httperror.Forbidden("Permission denied to access environment", err)
} }
@ -51,9 +52,11 @@ func (handler *Handler) endpointInspect(w http.ResponseWriter, r *http.Request)
endpointutils.UpdateEdgeEndpointHeartbeat(endpoint, settings) endpointutils.UpdateEdgeEndpointHeartbeat(endpoint, settings)
endpoint.ComposeSyntaxMaxVersion = handler.ComposeStackManager.ComposeSyntaxMaxVersion() endpoint.ComposeSyntaxMaxVersion = handler.ComposeStackManager.ComposeSyntaxMaxVersion()
if !excludeSnapshot(r) { excludeSnapshot, _ := request.RetrieveBooleanQueryParameter(r, "excludeSnapshot", true)
err = handler.SnapshotService.FillSnapshotData(endpoint) excludeRaw, _ := request.RetrieveBooleanQueryParameter(r, "excludeSnapshotRaw", true)
if err != nil {
if !excludeSnapshot {
if err := handler.SnapshotService.FillSnapshotData(endpoint, !excludeRaw); err != nil {
return httperror.InternalServerError("Unable to add snapshot data", err) return httperror.InternalServerError("Unable to add snapshot data", err)
} }
} }
@ -83,9 +86,3 @@ func (handler *Handler) endpointInspect(w http.ResponseWriter, r *http.Request)
return response.JSON(w, endpoint) return response.JSON(w, endpoint)
} }
func excludeSnapshot(r *http.Request) bool {
excludeSnapshot, _ := request.RetrieveBooleanQueryParameter(r, "excludeSnapshot", true)
return excludeSnapshot
}

View file

@ -44,6 +44,7 @@ const (
// @param edgeDeviceUntrusted query bool false "if true, show only untrusted edge agents, if false show only trusted edge agents (relevant only for edge agents)" // @param edgeDeviceUntrusted query bool false "if true, show only untrusted edge agents, if false show only trusted edge agents (relevant only for edge agents)"
// @param edgeCheckInPassedSeconds query number false "if bigger then zero, show only edge agents that checked-in in the last provided seconds (relevant only for edge agents)" // @param edgeCheckInPassedSeconds query number false "if bigger then zero, show only edge agents that checked-in in the last provided seconds (relevant only for edge agents)"
// @param excludeSnapshots query bool false "if true, the snapshot data won't be retrieved" // @param excludeSnapshots query bool false "if true, the snapshot data won't be retrieved"
// @param excludeSnapshotRaw query bool false "if true, the SnapshotRaw field won't be retrieved"
// @param name query string false "will return only environments(endpoints) with this name" // @param name query string false "will return only environments(endpoints) with this name"
// @param edgeStackId query portainer.EdgeStackID false "will return the environements of the specified edge stack" // @param edgeStackId query portainer.EdgeStackID false "will return the environements of the specified edge stack"
// @param edgeStackStatus query string false "only applied when edgeStackId exists. Filter the returned environments based on their deployment status in the stack (not the environment status!)" Enum("Pending", "Ok", "Error", "Acknowledged", "Remove", "RemoteUpdateSuccess", "ImagesPulled") // @param edgeStackStatus query string false "only applied when edgeStackId exists. Filter the returned environments based on their deployment status in the stack (not the environment status!)" Enum("Pending", "Ok", "Error", "Acknowledged", "Remove", "RemoteUpdateSuccess", "ImagesPulled")
@ -59,6 +60,7 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
limit, _ := request.RetrieveNumericQueryParameter(r, "limit", true) limit, _ := request.RetrieveNumericQueryParameter(r, "limit", true)
sortField, _ := request.RetrieveQueryParameter(r, "sort", true) sortField, _ := request.RetrieveQueryParameter(r, "sort", true)
sortOrder, _ := request.RetrieveQueryParameter(r, "order", true) sortOrder, _ := request.RetrieveQueryParameter(r, "order", true)
excludeRaw, _ := request.RetrieveBooleanQueryParameter(r, "excludeSnapshotRaw", true)
endpointGroups, err := handler.DataStore.EndpointGroup().ReadAll() endpointGroups, err := handler.DataStore.EndpointGroup().ReadAll()
if err != nil { if err != nil {
@ -114,7 +116,7 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
endpointutils.UpdateEdgeEndpointHeartbeat(&paginatedEndpoints[idx], settings) endpointutils.UpdateEdgeEndpointHeartbeat(&paginatedEndpoints[idx], settings)
if !query.excludeSnapshots { if !query.excludeSnapshots {
if err := handler.SnapshotService.FillSnapshotData(&paginatedEndpoints[idx]); err != nil { if err := handler.SnapshotService.FillSnapshotData(&paginatedEndpoints[idx], !excludeRaw); err != nil {
return httperror.InternalServerError("Unable to add snapshot data", err) return httperror.InternalServerError("Unable to add snapshot data", err)
} }
} }

View file

@ -272,7 +272,7 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
} }
} }
if err := handler.SnapshotService.FillSnapshotData(endpoint); err != nil { if err := handler.SnapshotService.FillSnapshotData(endpoint, true); err != nil {
return httperror.InternalServerError("Unable to add snapshot data", err) return httperror.InternalServerError("Unable to add snapshot data", err)
} }

View file

@ -33,7 +33,7 @@ func (handler *Handler) systemNodesCount(w http.ResponseWriter, r *http.Request)
var nodes int var nodes int
for _, endpoint := range endpoints { for _, endpoint := range endpoints {
if err := snapshot.FillSnapshotData(handler.dataStore, &endpoint); err != nil { if err := snapshot.FillSnapshotData(handler.dataStore, &endpoint, false); err != nil {
return httperror.InternalServerError("Unable to add snapshot data", err) return httperror.InternalServerError("Unable to add snapshot data", err)
} }

View file

@ -224,7 +224,7 @@ func (transport *Transport) getDockerID() (string, error) {
if transport.snapshotService != nil { if transport.snapshotService != nil {
endpoint := portainer.Endpoint{ID: transport.endpoint.ID} endpoint := portainer.Endpoint{ID: transport.endpoint.ID}
if err := transport.snapshotService.FillSnapshotData(&endpoint); err == nil && len(endpoint.Snapshots) > 0 { if err := transport.snapshotService.FillSnapshotData(&endpoint, true); err == nil && len(endpoint.Snapshots) > 0 {
if dockerID, err := snapshot.FetchDockerID(endpoint.Snapshots[0]); err == nil { if dockerID, err := snapshot.FetchDockerID(endpoint.Snapshots[0]); err == nil {
transport.dockerID = dockerID transport.dockerID = dockerID
return dockerID, nil return dockerID, nil

View file

@ -170,8 +170,8 @@ func (service *Service) Create(snapshot portainer.Snapshot) error {
return service.dataStore.Snapshot().Create(&snapshot) return service.dataStore.Snapshot().Create(&snapshot)
} }
func (service *Service) FillSnapshotData(endpoint *portainer.Endpoint) error { func (service *Service) FillSnapshotData(endpoint *portainer.Endpoint, includeRaw bool) error {
return FillSnapshotData(service.dataStore, endpoint) return FillSnapshotData(service.dataStore, endpoint, includeRaw)
} }
func (service *Service) snapshotKubernetesEndpoint(endpoint *portainer.Endpoint) error { func (service *Service) snapshotKubernetesEndpoint(endpoint *portainer.Endpoint) error {
@ -328,8 +328,16 @@ func FetchDockerID(snapshot portainer.DockerSnapshot) (string, error) {
return info.Swarm.Cluster.ID, nil return info.Swarm.Cluster.ID, nil
} }
func FillSnapshotData(tx dataservices.DataStoreTx, endpoint *portainer.Endpoint) error { func FillSnapshotData(tx dataservices.DataStoreTx, endpoint *portainer.Endpoint, includeRaw bool) error {
snapshot, err := tx.Snapshot().Read(endpoint.ID) var snapshot *portainer.Snapshot
var err error
if includeRaw {
snapshot, err = tx.Snapshot().Read(endpoint.ID)
} else {
snapshot, err = tx.Snapshot().ReadWithoutSnapshotRaw(endpoint.ID)
}
if tx.IsErrObjectNotFound(err) { if tx.IsErrObjectNotFound(err) {
endpoint.Snapshots = []portainer.DockerSnapshot{} endpoint.Snapshots = []portainer.DockerSnapshot{}
endpoint.Kubernetes.Snapshots = []portainer.KubernetesSnapshot{} endpoint.Kubernetes.Snapshots = []portainer.KubernetesSnapshot{}

View file

@ -1622,7 +1622,7 @@ type (
Start() Start()
SetSnapshotInterval(snapshotInterval string) error SetSnapshotInterval(snapshotInterval string) error
SnapshotEndpoint(endpoint *Endpoint) error SnapshotEndpoint(endpoint *Endpoint) error
FillSnapshotData(endpoint *Endpoint) error FillSnapshotData(endpoint *Endpoint, includeRaw bool) error
} }
// SwarmStackManager represents a service to manage Swarm stacks // SwarmStackManager represents a service to manage Swarm stacks

View file

@ -4,5 +4,5 @@ import { useEnvironmentId } from './useEnvironmentId';
export function useCurrentEnvironment(force = true) { export function useCurrentEnvironment(force = true) {
const id = useEnvironmentId(force); const id = useEnvironmentId(force);
return useEnvironment(id); return useEnvironment(id, undefined, { excludeSnapshot: false });
} }

View file

@ -1,16 +1,16 @@
import { DockerSnapshot } from '@/react/docker/snapshots/types'; import { DockerSnapshot } from '@/react/docker/snapshots/types';
import { useIsPodman } from '@/react/portainer/environments/queries/useIsPodman';
import { import {
Environment, Environment,
PlatformType, PlatformType,
KubernetesSnapshot, KubernetesSnapshot,
ContainerEngine,
} from '@/react/portainer/environments/types'; } from '@/react/portainer/environments/types';
import { getPlatformType } from '@/react/portainer/environments/utils'; import { getPlatformType } from '@/react/portainer/environments/utils';
import { getDockerEnvironmentType } from '@/react/portainer/environments/utils/getDockerEnvironmentType'; import { getDockerEnvironmentType } from '@/react/portainer/environments/utils/getDockerEnvironmentType';
export function EngineVersion({ environment }: { environment: Environment }) { export function EngineVersion({ environment }: { environment: Environment }) {
const platform = getPlatformType(environment.Type); const platform = getPlatformType(environment.Type);
const isPodman = useIsPodman(environment.Id); const isPodman = environment.ContainerEngine === ContainerEngine.Podman;
switch (platform) { switch (platform) {
case PlatformType.Docker: case PlatformType.Docker:

View file

@ -110,6 +110,7 @@ export function EnvironmentList({ onClickBrowse, onRefresh }: Props) {
updateInformation: isBE, updateInformation: isBE,
edgeAsync: getEdgeAsyncValue(connectionTypes), edgeAsync: getEdgeAsyncValue(connectionTypes),
platformTypes, platformTypes,
excludeSnapshotRaw: true,
}; };
const queryWithSort = { const queryWithSort = {

View file

@ -42,6 +42,7 @@ export interface BaseEnvironmentsQueryParams {
edgeAsync?: boolean; edgeAsync?: boolean;
edgeDeviceUntrusted?: boolean; edgeDeviceUntrusted?: boolean;
excludeSnapshots?: boolean; excludeSnapshots?: boolean;
excludeSnapshotRaw?: boolean;
provisioned?: boolean; provisioned?: boolean;
name?: string; name?: string;
agentVersions?: string[]; agentVersions?: string[];
@ -119,9 +120,15 @@ export async function getAgentVersions() {
} }
} }
export async function getEndpoint(id: EnvironmentId) { export async function getEndpoint(
id: EnvironmentId,
excludeSnapshot = true,
excludeSnapshotRaw = true
) {
try { try {
const { data: endpoint } = await axios.get<Environment>(buildUrl(id)); const { data: endpoint } = await axios.get<Environment>(buildUrl(id), {
params: { excludeSnapshot, excludeSnapshotRaw },
});
return endpoint; return endpoint;
} catch (e) { } catch (e) {
throw parseAxiosError(e as Error); throw parseAxiosError(e as Error);

View file

@ -10,11 +10,20 @@ import { environmentQueryKeys } from './query-keys';
export function useEnvironment<T = Environment>( export function useEnvironment<T = Environment>(
environmentId?: EnvironmentId, environmentId?: EnvironmentId,
select?: (environment: Environment) => T, select?: (environment: Environment) => T,
options?: { autoRefreshRate?: number } options?: {
autoRefreshRate?: number;
excludeSnapshot?: boolean;
excludeSnapshotRaw?: boolean;
}
) { ) {
return useQuery( return useQuery(
environmentQueryKeys.item(environmentId!), environmentQueryKeys.item(environmentId!),
() => getEndpoint(environmentId!), () =>
getEndpoint(
environmentId!,
options?.excludeSnapshot ?? undefined,
options?.excludeSnapshotRaw ?? undefined
),
{ {
select, select,
...withError('Failed loading environment'), ...withError('Failed loading environment'),

View file

@ -3,7 +3,7 @@ import { EdgeGroupId, EdgeTypes } from '@/react/portainer/environments/types';
export function useEnvironments(edgeGroupIds: Array<EdgeGroupId>) { export function useEnvironments(edgeGroupIds: Array<EdgeGroupId>) {
const environmentsQuery = useEnvironmentList( const environmentsQuery = useEnvironmentList(
{ edgeGroupIds, types: EdgeTypes, pageLimit: 0 }, { edgeGroupIds, types: EdgeTypes, pageLimit: 0, excludeSnapshots: true },
{ {
enabled: edgeGroupIds.length > 0, enabled: edgeGroupIds.length > 0,
} }