Skip to content

Commit

Permalink
#
Browse files Browse the repository at this point in the history
Signed-off-by: abarreiro <abarreiro@vmware.com>
  • Loading branch information
adambarreiro committed May 22, 2023
1 parent 54ea7c8 commit 72f91f5
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 35 deletions.
121 changes: 99 additions & 22 deletions govcd/ui_plugin.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,102 @@
package govcd

import (
"archive/zip"
"crypto/sha256"
"encoding/json"
"fmt"
"github.com/vmware/go-vcloud-director/v2/types/v56"
"github.com/vmware/go-vcloud-director/v2/util"
"io"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
)

type UIPluginMetadata struct {
type UIPlugin struct {
UIPluginMetadata *types.UIPluginMetadata
client *Client
}

// CreateUIPlugin creates a new UI extension and sets the provided plugin metadata for it.
func (vcdClient *VCDClient) AddUIPlugin(pluginPath string) (*UIPlugin, error) {
if strings.TrimSpace(pluginPath) == "" {
return nil, fmt.Errorf("plugin path must not be empty")
}
uiPluginMetadataPayload, err := getPluginMetadata(pluginPath)
if err != nil {
return nil, fmt.Errorf("error retrieving the metadata for the given plugin %s: %s", pluginPath, err)
}
uiPluginMetadata, err := createUIPlugin(&vcdClient.Client, uiPluginMetadataPayload)
if err != nil {
return nil, fmt.Errorf("error creating the UI plugin: %s", err)
}
err = uiPluginMetadata.upload(pluginPath)
if err != nil {
return nil, fmt.Errorf("error uploading the UI plugin: %s", err)
}

return uiPluginMetadata, nil
}

// getPluginMetadata retrieves the UI Plugin Metadata information stored inside the given .zip file.
func getPluginMetadata(pluginPath string) (*types.UIPluginMetadata, error) {
archive, err := zip.OpenReader(filepath.Clean(pluginPath))
if err != nil {
return nil, err
}
defer func() {
if err := archive.Close(); err != nil {
util.Logger.Printf("Error closing ZIP file: %s\n", err)
}
}()

var manifest *zip.File
for _, f := range archive.File {
if f.Name == "manifest.json" {
manifest = f
break
}
}
if manifest == nil {
return nil, fmt.Errorf("")
}

manifestContents, err := manifest.Open()
if err != nil {
return nil, err
}
defer func() {
if err := manifestContents.Close(); err != nil {
util.Logger.Printf("Error closing manifest file: %s\n", err)
}
}()

manifestBytes, err := io.ReadAll(manifestContents)
if err != nil {
return nil, err
}

var unmarshaledJson map[string]interface{}
err = json.Unmarshal(manifestBytes, &unmarshaledJson)
if err != nil {
return nil, err
}

return &types.UIPluginMetadata{
Vendor: unmarshaledJson["vendor"].(string),
License: unmarshaledJson["license"].(string),
Link: unmarshaledJson["link"].(string),
PluginName: unmarshaledJson["name"].(string),
Version: unmarshaledJson["version"].(string),
Description: unmarshaledJson["description"].(string),
}, nil
}

// createUIPlugin creates a new UI extension and sets the provided plugin metadata for it.
// Only System administrator can create a UI extension.
func (vcdClient *VCDClient) CreateUIPlugin(uiPluginMetadata *types.UIPluginMetadata) (*UIPluginMetadata, error) {
client := vcdClient.Client
func createUIPlugin(client *Client, uiPluginMetadata *types.UIPluginMetadata) (*UIPlugin, error) {
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExtensionsUi
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
if err != nil {
Expand All @@ -30,9 +108,9 @@ func (vcdClient *VCDClient) CreateUIPlugin(uiPluginMetadata *types.UIPluginMetad
return nil, err
}

result := &UIPluginMetadata{
result := &UIPlugin{
UIPluginMetadata: &types.UIPluginMetadata{},
client: &vcdClient.Client,
client: client,
}

err = client.OpenApiPostItem(apiVersion, urlRef, nil, uiPluginMetadata, result.UIPluginMetadata, nil)
Expand All @@ -43,21 +121,13 @@ func (vcdClient *VCDClient) CreateUIPlugin(uiPluginMetadata *types.UIPluginMetad
return result, nil
}

// Upload uploads the given UI Plugin to VCD. Only the file name in the input types.UploadSpec is required.
// The size is calculated automatically if not provided.
func (ui *UIPluginMetadata) Upload(uploadSpec *types.UploadSpec) error {
if strings.TrimSpace(uploadSpec.FileName) == "" {
return fmt.Errorf("file name to upload must not be empty")
}
fileContents, err := os.ReadFile(filepath.Clean(uploadSpec.FileName))
// This function uploads the given UI Plugin to VCD. Only the plugin path is required.
func (ui *UIPlugin) upload(pluginPath string) error {
fileContents, err := os.ReadFile(filepath.Clean(pluginPath))
if err != nil {
return err
}

if uploadSpec.Size <= 0 {
uploadSpec.Size = len(fileContents)
}

endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExtensionsUiPlugin
apiVersion, err := ui.client.getOpenApiHighestElevatedVersion(endpoint)
if err != nil {
Expand All @@ -69,6 +139,13 @@ func (ui *UIPluginMetadata) Upload(uploadSpec *types.UploadSpec) error {
return err
}

uploadSpec := types.UploadSpec{
FileName: filepath.Base(pluginPath),
ChecksumAlgo: "sha256",
Checksum: fmt.Sprintf("%x", sha256.Sum256(fileContents)),
Size: len(fileContents),
}

headers, err := ui.client.OpenApiPostItemAndGetHeaders(apiVersion, urlRef, nil, uploadSpec, nil, nil)
if err != nil {
return err
Expand Down Expand Up @@ -107,22 +184,22 @@ func getTransferIdFromHeader(headers http.Header) (string, error) {
return matches[1], nil
}

func (*UIPluginMetadata) Publish(orgs types.OpenApiReferences) (types.OpenApiReferences, error) {
func (*UIPlugin) Publish(orgs types.OpenApiReferences) (types.OpenApiReferences, error) {
return nil, nil
}

func (*UIPluginMetadata) PublishAll() {
func (*UIPlugin) PublishAll() {

}

func (*UIPluginMetadata) Unpublish(orgs types.OpenApiReferences) (types.OpenApiReferences, error) {
func (*UIPlugin) Unpublish(orgs types.OpenApiReferences) (types.OpenApiReferences, error) {
return nil, nil
}

func (*UIPluginMetadata) UnpublishAll() {
func (*UIPlugin) UnpublishAll() {

}

func (*UIPluginMetadata) Delete() {
func (*UIPlugin) Delete() {

}
54 changes: 54 additions & 0 deletions govcd/ui_plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,63 @@ package govcd
import (
"net/http"
"net/textproto"
"reflect"
"testing"

"github.com/vmware/go-vcloud-director/v2/types/v56"
. "gopkg.in/check.v1"
)

func init() {
testingTags["plugin"] = "ui_plugin_test.go"
}

func (vcd *TestVCD) Test_UIPlugin(check *C) {
_, err := vcd.client.AddUIPlugin("")
check.Assert(err, IsNil)
}

// Test_getPluginMetadata tests that getPluginMetadata can retrieve correctly the UI plugin metadata information
// stored inside the ZIP file.
func Test_getPluginMetadata(t *testing.T) {
tests := []struct {
name string
pluginPath string
want *types.UIPluginMetadata
wantErr bool
}{
{
name: "get ui plugin metadata",
pluginPath: "../test-resources/ui_plugin.zip",
want: &types.UIPluginMetadata{
Vendor: "VMware",
License: "Copyright (C) VMware 2022. All rights reserved.",
Link: "http://www.vmware.com/support",
PluginName: "Kubernetes Container Clusters",
Version: "4.0.0",
Description: "Kubernetes Container Clusters UI Plugin for CSE",
},
},
{
name: "invalid plugin",
pluginPath: "../test-resources/udf_test.iso",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := getPluginMetadata(tt.pluginPath)
if (err != nil) != tt.wantErr {
t.Errorf("getPluginMetadata() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("getPluginMetadata() got = %v, want %v", got, tt.want)
}
})
}
}

// Test_getTransferIdFromHeader tests that getTransferIdFromHeader can retrieve correctly a transfer ID from the headers
// of any HTTP response.
func Test_getTransferIdFromHeader(t *testing.T) {
Expand Down
Binary file added test-resources/ui_plugin.zip
Binary file not shown.
26 changes: 13 additions & 13 deletions types/v56/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,21 +468,21 @@ type DefinedEntity struct {

// UIPluginMetadata gives meta information about a UI Plugin
type UIPluginMetadata struct {
Vendor string `json:"vendor,omitempty"`
License string `json:"license,omitempty"`
Link string `json:"link,omitempty"`
PluginName string `json:"pluginName,omitempty"`
Version string `json:"version,omitempty"`
Description *string `json:"description,omitempty"`
ProviderScoped *bool `json:"provider_scoped,omitempty"`
TenantScoped *bool `json:"tenant_scoped,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
Vendor string `json:"vendor,omitempty"`
License string `json:"license,omitempty"`
Link string `json:"link,omitempty"`
PluginName string `json:"pluginName,omitempty"`
Version string `json:"version,omitempty"`
Description string `json:"description,omitempty"`
ProviderScoped *bool `json:"provider_scoped,omitempty"`
TenantScoped *bool `json:"tenant_scoped,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
}

// UploadSpec gives information about an upload
type UploadSpec struct {
FileName string `json:"fileName,omitempty"`
Size int `json:"size,omitempty"`
Checksum *string `json:"checksum,omitempty"`
ChecksumAlgo *string `json:"checksumAlgo,omitempty"`
FileName string `json:"fileName,omitempty"`
Size int `json:"size,omitempty"`
Checksum string `json:"checksum,omitempty"`
ChecksumAlgo string `json:"checksumAlgo,omitempty"`
}

0 comments on commit 72f91f5

Please sign in to comment.