mirror of https://github.com/portainer/portainer
parent
72117693fb
commit
db16299aab
|
@ -1,13 +1,14 @@
|
|||
package migrator
|
||||
|
||||
func (m *Migrator) migrateDBVersionTo30() error {
|
||||
if err := m.migrateSettings(); err != nil {
|
||||
func (m *Migrator) migrateDBVersionToDB30() error {
|
||||
if err := m.migrateSettingsToDB30(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) migrateSettings() error {
|
||||
func (m *Migrator) migrateSettingsToDB30() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
|
@ -76,7 +76,7 @@ func TestMigrateSettings(t *testing.T) {
|
|||
db: dbConn,
|
||||
settingsService: settingsService,
|
||||
}
|
||||
if err := m.migrateSettings(); err != nil {
|
||||
if err := m.migrateSettingsToDB30(); err != nil {
|
||||
t.Errorf("failed to update settings: %v", err)
|
||||
}
|
||||
updatedSettings, err := m.settingsService.Settings()
|
|
@ -1,11 +1,15 @@
|
|||
package migrator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/errors"
|
||||
endpointutils "github.com/portainer/portainer/api/internal/endpoint"
|
||||
snapshotutils "github.com/portainer/portainer/api/internal/snapshot"
|
||||
)
|
||||
|
||||
func (m *Migrator) migrateDBVersionTo32() error {
|
||||
func (m *Migrator) migrateDBVersionToDB32() error {
|
||||
err := m.updateRegistriesToDB32()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -16,6 +20,10 @@ func (m *Migrator) migrateDBVersionTo32() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := m.updateVolumeResourceControlToDB32(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -122,3 +130,84 @@ func (m *Migrator) updateDockerhubToDB32() error {
|
|||
|
||||
return m.registryService.CreateRegistry(registry)
|
||||
}
|
||||
|
||||
func (m *Migrator) updateVolumeResourceControlToDB32() error {
|
||||
endpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed fetching endpoints: %w", err)
|
||||
}
|
||||
|
||||
resourceControls, err := m.resourceControlService.ResourceControls()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed fetching resource controls: %w", err)
|
||||
}
|
||||
|
||||
toUpdate := map[portainer.ResourceControlID]string{}
|
||||
volumeResourceControls := map[string]*portainer.ResourceControl{}
|
||||
|
||||
for i := range resourceControls {
|
||||
resourceControl := resourceControls[i]
|
||||
if resourceControl.Type == portainer.VolumeResourceControl {
|
||||
volumeResourceControls[resourceControl.ResourceID] = &resourceControl
|
||||
}
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if !endpointutils.IsDockerEndpoint(&endpoint) {
|
||||
continue
|
||||
}
|
||||
|
||||
totalSnapshots := len(endpoint.Snapshots)
|
||||
if totalSnapshots == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
snapshot := endpoint.Snapshots[totalSnapshots-1]
|
||||
|
||||
endpointDockerID, err := snapshotutils.FetchDockerID(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed fetching endpoint docker id: %w", err)
|
||||
}
|
||||
|
||||
if volumesData, done := snapshot.SnapshotRaw.Volumes.(map[string]interface{}); done {
|
||||
if volumesData["Volumes"] == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
findResourcesToUpdateForDB32(endpointDockerID, volumesData, toUpdate, volumeResourceControls)
|
||||
}
|
||||
}
|
||||
|
||||
for _, resourceControl := range volumeResourceControls {
|
||||
if newResourceID, ok := toUpdate[resourceControl.ID]; ok {
|
||||
resourceControl.ResourceID = newResourceID
|
||||
err := m.resourceControlService.UpdateResourceControl(resourceControl.ID, resourceControl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed updating resource control %d: %w", resourceControl.ID, err)
|
||||
}
|
||||
|
||||
} else {
|
||||
err := m.resourceControlService.DeleteResourceControl(resourceControl.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed deleting resource control %d: %w", resourceControl.ID, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findResourcesToUpdateForDB32(dockerID string, volumesData map[string]interface{}, toUpdate map[portainer.ResourceControlID]string, volumeResourceControls map[string]*portainer.ResourceControl) {
|
||||
volumes := volumesData["Volumes"].([]interface{})
|
||||
for _, volumeMeta := range volumes {
|
||||
volume := volumeMeta.(map[string]interface{})
|
||||
volumeName := volume["Name"].(string)
|
||||
oldResourceID := fmt.Sprintf("%s%s", volumeName, volume["CreatedAt"].(string))
|
||||
resourceControl, ok := volumeResourceControls[oldResourceID]
|
||||
|
||||
if ok {
|
||||
toUpdate[resourceControl.ID] = fmt.Sprintf("%s_%s", volumeName, dockerID)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -367,7 +367,7 @@ func (m *Migrator) Migrate() error {
|
|||
|
||||
// Portainer 2.6.0
|
||||
if m.currentDBVersion < 30 {
|
||||
err := m.migrateDBVersionTo30()
|
||||
err := m.migrateDBVersionToDB30()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -375,7 +375,7 @@ func (m *Migrator) Migrate() error {
|
|||
|
||||
// Portainer 2.9.0
|
||||
if m.currentDBVersion < 32 {
|
||||
err := m.migrateDBVersionTo32()
|
||||
err := m.migrateDBVersionToDB32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package snapshot
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
|
@ -187,3 +188,27 @@ func (service *Service) snapshotEndpoints() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchDockerID fetches info.Swarm.Cluster.ID if endpoint is swarm and info.ID otherwise
|
||||
func FetchDockerID(snapshot portainer.DockerSnapshot) (string, error) {
|
||||
info, done := snapshot.SnapshotRaw.Info.(map[string]interface{})
|
||||
if !done {
|
||||
return "", errors.New("failed getting snapshot info")
|
||||
}
|
||||
|
||||
if !snapshot.Swarm {
|
||||
return info["ID"].(string), nil
|
||||
}
|
||||
|
||||
if info["Swarm"] == nil {
|
||||
return "", errors.New("swarm endpoint is missing swarm info snapshot")
|
||||
}
|
||||
|
||||
swarmInfo := info["Swarm"].(map[string]interface{})
|
||||
if swarmInfo["Cluster"] == nil {
|
||||
return "", errors.New("swarm endpoint is missing cluster info snapshot")
|
||||
}
|
||||
|
||||
clusterInfo := swarmInfo["Cluster"].(map[string]interface{})
|
||||
return clusterInfo["ID"].(string), nil
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ export function VolumeViewModel(data) {
|
|||
}
|
||||
this.Mountpoint = data.Mountpoint;
|
||||
|
||||
this.ResourceId = data.ResourceID;
|
||||
|
||||
if (data.Portainer) {
|
||||
if (data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
</div>
|
||||
|
||||
<!-- access-control-panel -->
|
||||
<por-access-control-panel ng-if="volume" resource-id="volume.Id + volume.CreatedAt" resource-control="volume.ResourceControl" resource-type="'volume'"> </por-access-control-panel>
|
||||
<por-access-control-panel ng-if="volume" resource-id="volume.ResourceId" resource-control="volume.ResourceControl" resource-type="'volume'"> </por-access-control-panel>
|
||||
<!-- !access-control-panel -->
|
||||
|
||||
<div class="row" ng-if="!(volume.Options | emptyobject)">
|
||||
|
|
Loading…
Reference in New Issue