Skip to content

Commit

Permalink
Init
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 de4d321 commit 54ea7c8
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 1 deletion.
2 changes: 1 addition & 1 deletion govcd/api_vcd_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build api || openapi || functional || catalog || vapp || gateway || network || org || query || extnetwork || task || vm || vdc || system || disk || lb || lbAppRule || lbAppProfile || lbServerPool || lbServiceMonitor || lbVirtualServer || user || search || nsxv || nsxt || auth || affinity || role || alb || certificate || vdcGroup || metadata || providervdc || rde || ALL
//go:build api || openapi || functional || catalog || vapp || gateway || network || org || query || extnetwork || task || vm || vdc || system || disk || lb || lbAppRule || lbAppProfile || lbServerPool || lbServiceMonitor || lbVirtualServer || user || search || nsxv || nsxt || auth || affinity || role || alb || certificate || vdcGroup || metadata || providervdc || rde || plugin || ALL

/*
* Copyright 2022 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
Expand Down
7 changes: 7 additions & 0 deletions govcd/openapi_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ var endpointMinApiVersions = map[string]string{
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointRdeEntities: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointRdeEntitiesTypes: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointRdeEntitiesResolve: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExtensionsUi: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExtensionsUiPlugin: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExtensionsUiPublishAll: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExtensionsUiPublish: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExtensionsUiUnpublishAll: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExtensionsUiUnpublish: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointTransfer: "35.0", // VCD 10.2+

// NSX-T ALB (Advanced/AVI Load Balancer) support was introduced in 10.2
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAlbController: "35.0", // VCD 10.2+
Expand Down
128 changes: 128 additions & 0 deletions govcd/ui_plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package govcd

import (
"fmt"
"github.com/vmware/go-vcloud-director/v2/types/v56"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
)

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

// 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
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExtensionsUi
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
if err != nil {
return nil, err
}

urlRef, err := client.OpenApiBuildEndpoint(endpoint)
if err != nil {
return nil, err
}

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

err = client.OpenApiPostItem(apiVersion, urlRef, nil, uiPluginMetadata, result.UIPluginMetadata, nil)
if err != nil {
return nil, err
}

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))
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 {
return err
}

urlRef, err := ui.client.OpenApiBuildEndpoint(endpoint)
if err != nil {
return err
}

headers, err := ui.client.OpenApiPostItemAndGetHeaders(apiVersion, urlRef, nil, uploadSpec, nil, nil)
if err != nil {
return err
}

transferId, err := getTransferIdFromHeader(headers)
if err != nil {
return err
}

endpoint = types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointTransfer
apiVersion, err = ui.client.getOpenApiHighestElevatedVersion(endpoint)
if err != nil {
return err
}

urlRef, err = ui.client.OpenApiBuildEndpoint(endpoint, transferId)
if err != nil {
return err
}

return ui.client.OpenApiPutItem(apiVersion, urlRef, nil, fileContents, nil, nil)
}

// getTransferIdFromHeader retrieves a valid transfer ID from any given HTTP headers
func getTransferIdFromHeader(headers http.Header) (string, error) {
rawLinkContent := headers.Get("link")
if rawLinkContent == "" {
return "", fmt.Errorf("error during UI plugin upload, the POST call didn't return any transfer link")
}
linkRegex := regexp.MustCompile(`<\S+/transfer/(\S+)>`)
matches := linkRegex.FindStringSubmatch(rawLinkContent)
if len(matches) < 2 {
return "", fmt.Errorf("error during UI plugin upload, the POST call didn't return a valid transfer link: %s", rawLinkContent)
}
return matches[1], nil
}

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

func (*UIPluginMetadata) PublishAll() {

}

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

func (*UIPluginMetadata) UnpublishAll() {

}

func (*UIPluginMetadata) Delete() {

}
89 changes: 89 additions & 0 deletions govcd/ui_plugin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//go:build functional || openapi || plugin || ALL

package govcd

import (
"net/http"
"net/textproto"
"testing"
)

// Test_getTransferIdFromHeader tests that getTransferIdFromHeader can retrieve correctly a transfer ID from the headers
// of any HTTP response.
func Test_getTransferIdFromHeader(t *testing.T) {
tests := []struct {
name string
headers http.Header
want string
wantErr bool
}{
{
name: "valid link in header",
headers: http.Header{
textproto.CanonicalMIMEHeaderKey("link"): {
"<https://www.my-vcd.com/transfer/cb63b0f6-ba56-43a8-8fe3-a64f0b25e7e5/my-amazing-plugin1.0.zip>;rel=\"upload:default\";type=\"application/octet-stream\"",
},
},
want: "cb63b0f6-ba56-43a8-8fe3-a64f0b25e7e5/my-amazing-plugin1.0.zip",
wantErr: false,
},
{
name: "valid link in header with special URI",
headers: http.Header{
textproto.CanonicalMIMEHeaderKey("link"): {
"<my-vcd:8080/transfer/cb63b0f6-ba56-43a8-8fe3-a64f0b25e7e5/my-amazing-plugin1.1.zip>;rel=\"upload:default\";type=\"application/octet-stream\"",
},
},
want: "cb63b0f6-ba56-43a8-8fe3-a64f0b25e7e5/my-amazing-plugin1.1.zip",
wantErr: false,
},
{
name: "empty header",
headers: http.Header{
textproto.CanonicalMIMEHeaderKey("link"): {
"",
},
},
wantErr: true,
},
{
name: "empty link in header",
headers: http.Header{
textproto.CanonicalMIMEHeaderKey("link"): {
"<>;rel=\"upload:default\";type=\"application/octet-stream\"",
},
},
wantErr: true,
},
{
name: "no link part in header",
headers: http.Header{
textproto.CanonicalMIMEHeaderKey("link"): {
"rel=\"upload:default\";type=\"application/octet-stream\"",
},
},
wantErr: true,
},
{
name: "invalid header",
headers: http.Header{
textproto.CanonicalMIMEHeaderKey("link"): {
"Error",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := getTransferIdFromHeader(tt.headers)
if (err != nil) != tt.wantErr {
t.Errorf("getTransferIdFromHeader() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("getTransferIdFromHeader() got = %v, want %v", got, tt.want)
}
})
}
}
7 changes: 7 additions & 0 deletions types/v56/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,13 @@ const (
OpenApiEndpointRdeEntities = "entities/"
OpenApiEndpointRdeEntitiesTypes = "entities/types/"
OpenApiEndpointRdeEntitiesResolve = "entities/%s/resolve"
OpenApiEndpointExtensionsUi = "extensions/ui"
OpenApiEndpointExtensionsUiPlugin = "extensions/ui/%s/plugin"
OpenApiEndpointExtensionsUiPublishAll = "extensions/ui/%s/tenants/publishAll"
OpenApiEndpointExtensionsUiPublish = "extensions/ui/%s/tenants/publish"
OpenApiEndpointExtensionsUiUnpublishAll = "extensions/ui/%s/tenants/unpublishAll"
OpenApiEndpointExtensionsUiUnpublish = "extensions/ui/%s/tenants/unpublish"
OpenApiEndpointTransfer = "transfer/"

// NSX-T ALB related endpoints

Expand Down
21 changes: 21 additions & 0 deletions types/v56/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,24 @@ type DefinedEntity struct {
Owner *OpenApiReference `json:"owner,omitempty"` // The owner of the defined entity
Org *OpenApiReference `json:"org,omitempty"` // The organization of the defined entity.
}

// 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"`
}

// 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"`
}

0 comments on commit 54ea7c8

Please sign in to comment.