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

feat(docker/volumes): change how volume resource id is calculated (#5067)

[EE-494]
This commit is contained in:
Chaim Lev-Ari 2021-07-19 10:43:49 +03:00 committed by GitHub
parent 72117693fb
commit db16299aab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 187 additions and 31 deletions

View file

@ -161,9 +161,7 @@ func (transport *Transport) proxyAgentRequest(r *http.Request) (*http.Response,
volumeName := volumeIDParameter[0]
agentTargetHeader := r.Header.Get(portainer.PortainerAgentTargetHeader)
resourceID, err := transport.getVolumeResourceID(agentTargetHeader, volumeName)
resourceID, err := transport.getVolumeResourceID(volumeName)
if err != nil {
return nil, err
}
@ -300,7 +298,7 @@ func (transport *Transport) proxyServiceRequest(request *http.Request) (*http.Re
func (transport *Transport) proxyVolumeRequest(request *http.Request) (*http.Response, error) {
switch requestPath := request.URL.Path; requestPath {
case "/volumes/create":
return transport.decorateVolumeResourceCreationOperation(request, volumeObjectIdentifier, portainer.VolumeResourceControl)
return transport.decorateVolumeResourceCreationOperation(request, portainer.VolumeResourceControl)
case "/volumes/prune":
return transport.administratorOperation(request)

View file

@ -3,6 +3,7 @@ package docker
import (
"context"
"errors"
"fmt"
"net/http"
"path"
@ -12,10 +13,11 @@ import (
"github.com/portainer/portainer/api/http/proxy/factory/utils"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/portainer/portainer/api/internal/snapshot"
)
const (
volumeObjectIdentifier = "ID"
volumeObjectIdentifier = "ResourceID"
)
func getInheritedResourceControlFromVolumeLabels(dockerClient *client.Client, endpointID portainer.EndpointID, volumeID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
@ -48,10 +50,12 @@ func (transport *Transport) volumeListOperation(response *http.Response, executo
for _, volumeObject := range volumeData {
volume := volumeObject.(map[string]interface{})
if volume["Name"] == nil || volume["CreatedAt"] == nil {
return errors.New("missing identifier in Docker resource list response")
err = transport.decorateVolumeResponseWithResourceID(volume)
if err != nil {
return fmt.Errorf("failed decorating volume response: %w", err)
}
volume[volumeObjectIdentifier] = volume["Name"].(string) + volume["CreatedAt"].(string)
}
resourceOperationParameters := &resourceOperationParameters{
@ -81,10 +85,10 @@ func (transport *Transport) volumeInspectOperation(response *http.Response, exec
return err
}
if responseObject["Name"] == nil || responseObject["CreatedAt"] == nil {
return errors.New("missing identifier in Docker resource detail response")
err = transport.decorateVolumeResponseWithResourceID(responseObject)
if err != nil {
return fmt.Errorf("failed decorating volume response: %w", err)
}
responseObject[volumeObjectIdentifier] = responseObject["Name"].(string) + responseObject["CreatedAt"].(string)
resourceOperationParameters := &resourceOperationParameters{
resourceIdentifierAttribute: volumeObjectIdentifier,
@ -95,6 +99,21 @@ func (transport *Transport) volumeInspectOperation(response *http.Response, exec
return transport.applyAccessControlOnResource(resourceOperationParameters, responseObject, response, executor)
}
func (transport *Transport) decorateVolumeResponseWithResourceID(responseObject map[string]interface{}) error {
if responseObject["Name"] == nil {
return errors.New("missing identifier in Docker resource detail response")
}
resourceID, err := transport.getVolumeResourceID(responseObject["Name"].(string))
if err != nil {
return fmt.Errorf("failed fetching resource id: %w", err)
}
responseObject[volumeObjectIdentifier] = resourceID
return nil
}
// selectorVolumeLabels retrieve the labels object associated to the volume object.
// Labels are available under the "Labels" property.
// API schema references:
@ -104,7 +123,7 @@ func selectorVolumeLabels(responseObject map[string]interface{}) map[string]inte
return utils.GetJSONObject(responseObject, "Labels")
}
func (transport *Transport) decorateVolumeResourceCreationOperation(request *http.Request, resourceIdentifierAttribute string, resourceType portainer.ResourceControlType) (*http.Response, error) {
func (transport *Transport) decorateVolumeResourceCreationOperation(request *http.Request, resourceType portainer.ResourceControlType) (*http.Response, error) {
tokenData, err := security.RetrieveTokenData(request)
if err != nil {
return nil, err
@ -136,27 +155,33 @@ func (transport *Transport) decorateVolumeResourceCreationOperation(request *htt
}
if response.StatusCode == http.StatusCreated {
err = transport.decorateVolumeCreationResponse(response, resourceIdentifierAttribute, resourceType, tokenData.ID)
err = transport.decorateVolumeCreationResponse(response, resourceType, tokenData.ID)
}
return response, err
}
func (transport *Transport) decorateVolumeCreationResponse(response *http.Response, resourceIdentifierAttribute string, resourceType portainer.ResourceControlType, userID portainer.UserID) error {
func (transport *Transport) decorateVolumeCreationResponse(response *http.Response, resourceType portainer.ResourceControlType, userID portainer.UserID) error {
responseObject, err := utils.GetResponseAsJSONObject(response)
if err != nil {
return err
}
if responseObject["Name"] == nil || responseObject["CreatedAt"] == nil {
if responseObject["Name"] == nil {
return errors.New("missing identifier in Docker resource creation response")
}
resourceID := responseObject["Name"].(string) + responseObject["CreatedAt"].(string)
resourceID, err := transport.getVolumeResourceID(responseObject["Name"].(string))
if err != nil {
return fmt.Errorf("failed fetching resource id: %w", err)
}
resourceControl, err := transport.createPrivateResourceControl(resourceID, resourceType, userID)
if err != nil {
return err
}
responseObject[volumeObjectIdentifier] = resourceID
responseObject = decorateObject(responseObject, resourceControl)
return utils.RewriteResponse(response, responseObject, http.StatusOK)
@ -169,9 +194,8 @@ func (transport *Transport) restrictedVolumeOperation(requestPath string, reques
}
volumeName := path.Base(requestPath)
agentTargetHeader := request.Header.Get(portainer.PortainerAgentTargetHeader)
resourceID, err := transport.getVolumeResourceID(agentTargetHeader, volumeName)
resourceID, err := transport.getVolumeResourceID(volumeName)
if err != nil {
return nil, err
}
@ -182,17 +206,34 @@ func (transport *Transport) restrictedVolumeOperation(requestPath string, reques
return transport.restrictedResourceOperation(request, resourceID, volumeName, portainer.VolumeResourceControl, false)
}
func (transport *Transport) getVolumeResourceID(nodename, volumeID string) (string, error) {
cli, err := transport.dockerClientFactory.CreateClient(transport.endpoint, nodename)
func (transport *Transport) getVolumeResourceID(volumeName string) (string, error) {
dockerID, err := transport.getDockerID()
if err != nil {
return "", err
return "", fmt.Errorf("failed fetching docker id: %w", err)
}
return fmt.Sprintf("%s_%s", volumeName, dockerID), nil
}
func (transport *Transport) getDockerID() (string, error) {
if len(transport.endpoint.Snapshots) > 0 {
dockerID, err := snapshot.FetchDockerID(transport.endpoint.Snapshots[0])
// ignore err - in case of error, just generate not from snapshot
if err == nil {
return dockerID, nil
}
}
cli := transport.dockerClient
defer cli.Close()
volume, err := cli.VolumeInspect(context.Background(), volumeID)
info, err := cli.Info(context.Background())
if err != nil {
return "", err
}
return volume.Name + volume.CreatedAt, nil
if info.Swarm.Cluster != nil {
return info.Swarm.Cluster.ID, nil
}
return info.ID, nil
}