Move aci client (#531)
* Add azure-aci client dep * Use aci client from new repo
This commit is contained in:
@@ -20,11 +20,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
client "github.com/virtual-kubelet/azure-aci/client"
|
||||
"github.com/virtual-kubelet/azure-aci/client/aci"
|
||||
"github.com/virtual-kubelet/azure-aci/client/network"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||
client "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/aci"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/network"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/trace"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
k8serr "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -783,7 +783,8 @@ func (p *ACIProvider) ExecInContainer(name string, uid types.UID, container stri
|
||||
terminalSize = <-resize // Receive terminal resize event if resize stream is present
|
||||
}
|
||||
|
||||
xcrsp, err := p.aciClient.LaunchExec(p.resourceGroup, cg.Name, container, cmd[0], terminalSize)
|
||||
ts := aci.TerminalSizeRequest{Height: int(terminalSize.Height), Width: int(terminalSize.Width)}
|
||||
xcrsp, err := p.aciClient.LaunchExec(p.resourceGroup, cg.Name, container, cmd[0], ts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/aci"
|
||||
"github.com/virtual-kubelet/azure-aci/client/aci"
|
||||
)
|
||||
|
||||
// ACIMock implements a Azure Container Instance mock server.
|
||||
|
||||
@@ -18,10 +18,10 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
azure "github.com/virtual-kubelet/azure-aci/client"
|
||||
"github.com/virtual-kubelet/azure-aci/client/aci"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/aci"
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# A half-baked SDK for Azure in Go
|
||||
|
||||
This is a half-baked (ie. only provides what we needed) SDK for Azure in Go.
|
||||
|
||||
## Authentication
|
||||
|
||||
### Use an authentication file
|
||||
|
||||
This SDK also supports authentication with a JSON file containing credentials for the service principal. In the Azure CLI, you can create a service principal and its authentication file with this command:
|
||||
|
||||
``` bash
|
||||
az ad sp create-for-rbac --sdk-auth > mycredentials.json
|
||||
```
|
||||
|
||||
Save this file in a secure location on your system where your code can read it. Set an environment variable with the full path to the file:
|
||||
|
||||
``` bash
|
||||
export AZURE_AUTH_LOCATION=/secure/location/mycredentials.json
|
||||
```
|
||||
|
||||
``` powershell
|
||||
$env:AZURE_AUTH_LOCATION= "/secure/location/mycredentials.json"
|
||||
```
|
||||
|
||||
The file looks like this, in case you want to create it yourself:
|
||||
|
||||
``` json
|
||||
{
|
||||
"clientId": "<your service principal client ID>",
|
||||
"clientSecret": "your service principal client secret",
|
||||
"subscriptionId": "<your Azure Subsription ID>",
|
||||
"tenantId": "<your tenant ID>",
|
||||
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
|
||||
"resourceManagerEndpointUrl": "https://management.azure.com/",
|
||||
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
|
||||
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
|
||||
"galleryEndpointUrl": "https://gallery.azure.com/",
|
||||
"managementEndpointUrl": "https://management.core.windows.net/"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Log Analytics support
|
||||
|
||||
Log Analytics is supported through environment variables:
|
||||
- `LOG_ANALYTICS_KEY`
|
||||
- `LOG_ANALYTICS_ID`
|
||||
|
||||
You can also specify a file with these values and specify the path to it in the `LOG_ANALYTICS_AUTH_LOCATION`:
|
||||
|
||||
``` bash
|
||||
export LOG_ANALYTICS_AUTH_LOCATION=/secure/location/loganalytics.json
|
||||
```
|
||||
|
||||
``` powershell
|
||||
$env:LOG_ANALYTICS_AUTH_LOCATION= "/secure/location/loganalytics.json"
|
||||
```
|
||||
|
||||
The file should look like this:
|
||||
|
||||
``` json
|
||||
{
|
||||
"workspaceID": "<YOUR_LOG_ANALYTICS_WORKSPACE_ID>",
|
||||
"workspaceKey": "<YOUR_LOG_ANALYTICS_WORKSPACE_KEY>"
|
||||
}
|
||||
```
|
||||
@@ -1,39 +0,0 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func NewContainerGroupDiagnostics(logAnalyticsID, logAnalyticsKey string) (*ContainerGroupDiagnostics, error) {
|
||||
|
||||
if logAnalyticsID == "" || logAnalyticsKey == "" {
|
||||
return nil, errors.New("Log Analytics configuration requires both the workspace ID and Key")
|
||||
}
|
||||
|
||||
return &ContainerGroupDiagnostics{
|
||||
LogAnalytics: &LogAnalyticsWorkspace{
|
||||
WorkspaceID: logAnalyticsID,
|
||||
WorkspaceKey: logAnalyticsKey,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewContainerGroupDiagnosticsFromFile(filepath string) (*ContainerGroupDiagnostics, error) {
|
||||
|
||||
analyticsdata, err := ioutil.ReadFile(filepath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Reading Log Analytics Auth file %q failed: %v", filepath, err)
|
||||
}
|
||||
// Unmarshal the log analytics file.
|
||||
var law LogAnalyticsWorkspace
|
||||
if err := json.Unmarshal(analyticsdata, &law); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ContainerGroupDiagnostics{
|
||||
LogAnalytics: &law,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLogAnalyticsFileParsingSuccess(t *testing.T) {
|
||||
diagnostics, err := NewContainerGroupDiagnosticsFromFile("../../../../loganalytics.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if diagnostics == nil || diagnostics.LogAnalytics == nil {
|
||||
t.Fatalf("Unexpected nil diagnostics. Log Analytics file not parsed correctly")
|
||||
}
|
||||
|
||||
if diagnostics.LogAnalytics.WorkspaceID == "" || diagnostics.LogAnalytics.WorkspaceKey == "" {
|
||||
t.Fatalf("Unexpected empty analytics authentication credentials. Log Analytics file not parsed correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogAnalyticsFileParsingFailure(t *testing.T) {
|
||||
tempFile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = NewContainerGroupDiagnosticsFromFile(tempFile.Name())
|
||||
|
||||
// Cleaup
|
||||
tempFile.Close()
|
||||
os.Remove(tempFile.Name())
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected parsing an empty Log Analytics auth file to fail, but there were no errors")
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.opencensus.io/plugin/ochttp/propagation/b3"
|
||||
|
||||
"go.opencensus.io/plugin/ochttp"
|
||||
|
||||
azure "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
)
|
||||
|
||||
const (
|
||||
// BaseURI is the default URI used for compute services.
|
||||
baseURI = "https://management.azure.com"
|
||||
defaultUserAgent = "virtual-kubelet/azure-arm-aci/2018-09-01"
|
||||
apiVersion = "2018-09-01"
|
||||
|
||||
containerGroupURLPath = "subscriptions/{{.subscriptionId}}/resourceGroups/{{.resourceGroup}}/providers/Microsoft.ContainerInstance/containerGroups/{{.containerGroupName}}"
|
||||
containerGroupListURLPath = "subscriptions/{{.subscriptionId}}/providers/Microsoft.ContainerInstance/containerGroups"
|
||||
containerGroupListByResourceGroupURLPath = "subscriptions/{{.subscriptionId}}/resourceGroups/{{.resourceGroup}}/providers/Microsoft.ContainerInstance/containerGroups"
|
||||
containerLogsURLPath = containerGroupURLPath + "/containers/{{.containerName}}/logs"
|
||||
containerExecURLPath = containerGroupURLPath + "/containers/{{.containerName}}/exec"
|
||||
containerGroupMetricsURLPath = containerGroupURLPath + "/providers/microsoft.Insights/metrics"
|
||||
)
|
||||
|
||||
// Client is a client for interacting with Azure Container Instances.
|
||||
//
|
||||
// Clients should be reused instead of created as needed.
|
||||
// The methods of Client are safe for concurrent use by multiple goroutines.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
auth *azure.Authentication
|
||||
}
|
||||
|
||||
// NewClient creates a new Azure Container Instances client with extra user agent.
|
||||
func NewClient(auth *azure.Authentication, extraUserAgent string) (*Client, error) {
|
||||
if auth == nil {
|
||||
return nil, fmt.Errorf("Authentication is not supplied for the Azure client")
|
||||
}
|
||||
|
||||
userAgent := []string{defaultUserAgent}
|
||||
if extraUserAgent != "" {
|
||||
userAgent = append(userAgent, extraUserAgent)
|
||||
}
|
||||
|
||||
client, err := azure.NewClient(auth, baseURI, userAgent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating Azure client failed: %v", err)
|
||||
}
|
||||
hc := client.HTTPClient
|
||||
hc.Transport = &ochttp.Transport{
|
||||
Base: hc.Transport,
|
||||
Propagation: &b3.HTTPFormat{},
|
||||
NewClientTrace: ochttp.NewSpanAnnotatingClientTrace,
|
||||
}
|
||||
|
||||
return &Client{hc: client.HTTPClient, auth: auth}, nil
|
||||
}
|
||||
@@ -1,591 +0,0 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
azure "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/resourcegroups"
|
||||
)
|
||||
|
||||
var (
|
||||
client *Client
|
||||
location = "westus"
|
||||
resourceGroup = "virtual-kubelet-tests"
|
||||
containerGroup = "virtual-kubelet-test-container-group"
|
||||
subscriptionID string
|
||||
)
|
||||
|
||||
func init() {
|
||||
//Create a resource group name with uuid.
|
||||
uid := uuid.New()
|
||||
resourceGroup += "-" + uid.String()[0:6]
|
||||
}
|
||||
|
||||
// The TestMain function creates a resource group for testing
|
||||
// and deletes in when it's done.
|
||||
func TestMain(m *testing.M) {
|
||||
auth, err := azure.NewAuthenticationFromFile("../../../../credentials.json")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load Azure authentication file: %v", err)
|
||||
}
|
||||
|
||||
subscriptionID = auth.SubscriptionID
|
||||
|
||||
// Check if the resource group exists and create it if not.
|
||||
rgCli, err := resourcegroups.NewClient(auth, "unit-test")
|
||||
if err != nil {
|
||||
log.Fatalf("creating new resourcegroups client failed: %v", err)
|
||||
}
|
||||
|
||||
// Check if the resource group exists.
|
||||
exists, err := rgCli.ResourceGroupExists(resourceGroup)
|
||||
if err != nil {
|
||||
log.Fatalf("checking if resource group exists failed: %v", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
// Create the resource group.
|
||||
_, err := rgCli.CreateResourceGroup(resourceGroup, resourcegroups.Group{
|
||||
Location: location,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("creating resource group failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Run the tests.
|
||||
merr := m.Run()
|
||||
|
||||
// Delete the resource group.
|
||||
if err := rgCli.DeleteResourceGroup(resourceGroup); err != nil {
|
||||
log.Printf("Couldn't delete resource group %q: %v", resourceGroup, err)
|
||||
|
||||
}
|
||||
|
||||
if merr != 0 {
|
||||
os.Exit(merr)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
auth, err := azure.NewAuthenticationFromFile("../../../../credentials.json")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load Azure authentication file: %v", err)
|
||||
}
|
||||
|
||||
c, err := NewClient(auth, "unit-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client = c
|
||||
}
|
||||
|
||||
func TestCreateContainerGroupFails(t *testing.T) {
|
||||
_, err := client.CreateContainerGroup(context.Background(), resourceGroup, containerGroup, ContainerGroup{
|
||||
Location: location,
|
||||
ContainerGroupProperties: ContainerGroupProperties{
|
||||
OsType: Linux,
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
ContainerProperties: ContainerProperties{
|
||||
Image: "nginx",
|
||||
Command: []string{"nginx", "-g", "daemon off;"},
|
||||
Ports: []ContainerPort{
|
||||
{
|
||||
Protocol: ContainerNetworkProtocolTCP,
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected create container group to fail with ResourceRequestsNotSpecified, but returned nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "ResourceRequestsNotSpecified") {
|
||||
t.Fatalf("expected ResourceRequestsNotSpecified to be in the error message but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerGroupWithoutResourceLimit(t *testing.T) {
|
||||
cg, err := client.CreateContainerGroup(context.Background(), resourceGroup, containerGroup, ContainerGroup{
|
||||
Location: location,
|
||||
ContainerGroupProperties: ContainerGroupProperties{
|
||||
OsType: Linux,
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
ContainerProperties: ContainerProperties{
|
||||
Image: "nginx",
|
||||
Command: []string{"nginx", "-g", "daemon off;"},
|
||||
Ports: []ContainerPort{
|
||||
{
|
||||
Protocol: ContainerNetworkProtocolTCP,
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
Resources: ResourceRequirements{
|
||||
Requests: &ResourceRequests{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cg.Name != containerGroup {
|
||||
t.Fatalf("resource group name is %s, expected %s", cg.Name, containerGroup)
|
||||
}
|
||||
|
||||
if err := client.DeleteContainerGroup(context.Background(), resourceGroup, containerGroup); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerGroup(t *testing.T) {
|
||||
cg, err := client.CreateContainerGroup(context.Background(), resourceGroup, containerGroup, ContainerGroup{
|
||||
Location: location,
|
||||
ContainerGroupProperties: ContainerGroupProperties{
|
||||
OsType: Linux,
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
ContainerProperties: ContainerProperties{
|
||||
Image: "nginx",
|
||||
Command: []string{"nginx", "-g", "daemon off;"},
|
||||
Ports: []ContainerPort{
|
||||
{
|
||||
Protocol: ContainerNetworkProtocolTCP,
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
Resources: ResourceRequirements{
|
||||
Requests: &ResourceRequests{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
Limits: &ResourceLimits{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cg.Name != containerGroup {
|
||||
t.Fatalf("resource group name is %s, expected %s", cg.Name, containerGroup)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerGroupWithBadVNetFails(t *testing.T) {
|
||||
_, err := client.CreateContainerGroup(context.Background(), resourceGroup, containerGroup, ContainerGroup{
|
||||
Location: location,
|
||||
ContainerGroupProperties: ContainerGroupProperties{
|
||||
OsType: Linux,
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
ContainerProperties: ContainerProperties{
|
||||
Image: "nginx",
|
||||
Command: []string{"nginx", "-g", "daemon off;"},
|
||||
Ports: []ContainerPort{
|
||||
{
|
||||
Protocol: ContainerNetworkProtocolTCP,
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
Resources: ResourceRequirements{
|
||||
Requests: &ResourceRequests{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
Limits: &ResourceLimits{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NetworkProfile: &NetworkProfileDefinition{
|
||||
ID: fmt.Sprintf(
|
||||
"/subscriptions/%s/resourceGroups/%s/providers"+
|
||||
"/Microsoft.Network/networkProfiles/%s",
|
||||
subscriptionID,
|
||||
resourceGroup,
|
||||
"badNetworkProfile",
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected create container group to fail with NetworkProfileNotFound, but returned nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "NetworkProfileNotFound") {
|
||||
t.Fatalf("expected NetworkProfileNotFound to be in the error message but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContainerGroup(t *testing.T) {
|
||||
cg, err, _ := client.GetContainerGroup(context.Background(), resourceGroup, containerGroup)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cg.Name != containerGroup {
|
||||
t.Fatalf("resource group name is %s, expected %s", cg.Name, containerGroup)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListContainerGroup(t *testing.T) {
|
||||
list, err := client.ListContainerGroups(context.Background(), resourceGroup)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, cg := range list.Value {
|
||||
if cg.Name != containerGroup {
|
||||
t.Fatalf("resource group name is %s, expected %s", cg.Name, containerGroup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerGroupWithLivenessProbe(t *testing.T) {
|
||||
uid := uuid.New()
|
||||
containerGroupName := containerGroup + "-" + uid.String()[0:6]
|
||||
cg, err := client.CreateContainerGroup(context.Background(), resourceGroup, containerGroupName, ContainerGroup{
|
||||
Location: location,
|
||||
ContainerGroupProperties: ContainerGroupProperties{
|
||||
OsType: Linux,
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
ContainerProperties: ContainerProperties{
|
||||
Image: "nginx",
|
||||
Command: []string{"nginx", "-g", "daemon off;"},
|
||||
Ports: []ContainerPort{
|
||||
{
|
||||
Protocol: ContainerNetworkProtocolTCP,
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
Resources: ResourceRequirements{
|
||||
Requests: &ResourceRequests{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
Limits: &ResourceLimits{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
},
|
||||
LivenessProbe: &ContainerProbe{
|
||||
HTTPGet: &ContainerHTTPGetProbe{
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cg.Name != containerGroupName {
|
||||
t.Fatalf("resource group name is %s, expected %s", cg.Name, containerGroupName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerGroupFailsWithLivenessProbeMissingPort(t *testing.T) {
|
||||
uid := uuid.New()
|
||||
containerGroupName := containerGroup + "-" + uid.String()[0:6]
|
||||
_, err := client.CreateContainerGroup(context.Background(), resourceGroup, containerGroupName, ContainerGroup{
|
||||
Location: location,
|
||||
ContainerGroupProperties: ContainerGroupProperties{
|
||||
OsType: Linux,
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
ContainerProperties: ContainerProperties{
|
||||
Image: "nginx",
|
||||
Command: []string{"nginx", "-g", "daemon off;"},
|
||||
Ports: []ContainerPort{
|
||||
{
|
||||
Protocol: ContainerNetworkProtocolTCP,
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
Resources: ResourceRequirements{
|
||||
Requests: &ResourceRequests{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
Limits: &ResourceLimits{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
},
|
||||
LivenessProbe: &ContainerProbe{
|
||||
HTTPGet: &ContainerHTTPGetProbe{
|
||||
Path: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected failure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerGroupWithReadinessProbe(t *testing.T) {
|
||||
uid := uuid.New()
|
||||
containerGroupName := containerGroup + "-" + uid.String()[0:6]
|
||||
cg, err := client.CreateContainerGroup(context.Background(), resourceGroup, containerGroupName, ContainerGroup{
|
||||
Location: location,
|
||||
ContainerGroupProperties: ContainerGroupProperties{
|
||||
OsType: Linux,
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
ContainerProperties: ContainerProperties{
|
||||
Image: "nginx",
|
||||
Command: []string{"nginx", "-g", "daemon off;"},
|
||||
Ports: []ContainerPort{
|
||||
{
|
||||
Protocol: ContainerNetworkProtocolTCP,
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
Resources: ResourceRequirements{
|
||||
Requests: &ResourceRequests{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
Limits: &ResourceLimits{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
},
|
||||
ReadinessProbe: &ContainerProbe{
|
||||
HTTPGet: &ContainerHTTPGetProbe{
|
||||
Port: 80,
|
||||
Path: "/",
|
||||
},
|
||||
InitialDelaySeconds: 5,
|
||||
SuccessThreshold: 3,
|
||||
FailureThreshold: 5,
|
||||
TimeoutSeconds: 120,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cg.Name != containerGroupName {
|
||||
t.Fatalf("resource group name is %s, expected %s", cg.Name, containerGroupName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerGroupWithLogAnalytics(t *testing.T) {
|
||||
diagnostics, err := NewContainerGroupDiagnosticsFromFile("../../../../loganalytics.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cgname := "cgla"
|
||||
cg, err := client.CreateContainerGroup(context.Background(), resourceGroup, cgname, ContainerGroup{
|
||||
Location: location,
|
||||
ContainerGroupProperties: ContainerGroupProperties{
|
||||
OsType: Linux,
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
ContainerProperties: ContainerProperties{
|
||||
Image: "nginx",
|
||||
Command: []string{"nginx", "-g", "daemon off;"},
|
||||
Ports: []ContainerPort{
|
||||
{
|
||||
Protocol: ContainerNetworkProtocolTCP,
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
Resources: ResourceRequirements{
|
||||
Requests: &ResourceRequests{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
Limits: &ResourceLimits{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Diagnostics: diagnostics,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cg.Name != cgname {
|
||||
t.Fatalf("resource group name is %s, expected %s", cg.Name, cgname)
|
||||
}
|
||||
if err := client.DeleteContainerGroup(context.Background(), resourceGroup, cgname); err != nil {
|
||||
t.Fatalf("Delete Container Group failed: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerGroupWithInvalidLogAnalytics(t *testing.T) {
|
||||
law := &LogAnalyticsWorkspace{}
|
||||
_, err := client.CreateContainerGroup(context.Background(), resourceGroup, containerGroup, ContainerGroup{
|
||||
Location: location,
|
||||
ContainerGroupProperties: ContainerGroupProperties{
|
||||
OsType: Linux,
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
ContainerProperties: ContainerProperties{
|
||||
Image: "nginx",
|
||||
Command: []string{"nginx", "-g", "daemon off;"},
|
||||
Ports: []ContainerPort{
|
||||
{
|
||||
Protocol: ContainerNetworkProtocolTCP,
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
Resources: ResourceRequirements{
|
||||
Requests: &ResourceRequests{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
Limits: &ResourceLimits{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Diagnostics: &ContainerGroupDiagnostics{
|
||||
LogAnalytics: law,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("TestCreateContainerGroupWithInvalidLogAnalytics should fail but encountered no errors")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerGroupWithVNet(t *testing.T) {
|
||||
uid := uuid.New()
|
||||
containerGroupName := containerGroup + "-" + uid.String()[0:6]
|
||||
fakeKubeConfig := base64.StdEncoding.EncodeToString([]byte(uid.String()))
|
||||
networkProfileId := "/subscriptions/ae43b1e3-c35d-4c8c-bc0d-f148b4c52b78/resourceGroups/aci-connector/providers/Microsoft.Network/networkprofiles/aci-connector-network-profile-westus"
|
||||
diagnostics, err := NewContainerGroupDiagnosticsFromFile("../../../../loganalytics.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diagnostics.LogAnalytics.LogType = LogAnlyticsLogTypeContainerInsights
|
||||
|
||||
cg, err := client.CreateContainerGroup(context.Background(), resourceGroup, containerGroupName, ContainerGroup{
|
||||
Location: location,
|
||||
ContainerGroupProperties: ContainerGroupProperties{
|
||||
OsType: Linux,
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
ContainerProperties: ContainerProperties{
|
||||
Image: "nginx",
|
||||
Command: []string{"nginx", "-g", "daemon off;"},
|
||||
Ports: []ContainerPort{
|
||||
{
|
||||
Protocol: ContainerNetworkProtocolTCP,
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
Resources: ResourceRequirements{
|
||||
Requests: &ResourceRequests{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
Limits: &ResourceLimits{
|
||||
CPU: 1,
|
||||
MemoryInGB: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NetworkProfile: &NetworkProfileDefinition{
|
||||
ID: networkProfileId,
|
||||
},
|
||||
Extensions: []*Extension{
|
||||
&Extension{
|
||||
Name: "kube-proxy",
|
||||
Properties: &ExtensionProperties{
|
||||
Type: ExtensionTypeKubeProxy,
|
||||
Version: ExtensionVersion1_0,
|
||||
Settings: map[string]string{
|
||||
KubeProxyExtensionSettingClusterCIDR: "10.240.0.0/16",
|
||||
KubeProxyExtensionSettingKubeVersion: KubeProxyExtensionKubeVersion,
|
||||
},
|
||||
ProtectedSettings: map[string]string{
|
||||
KubeProxyExtensionSettingKubeConfig: fakeKubeConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
DNSConfig: &DNSConfig{
|
||||
NameServers: []string{"1.1.1.1"},
|
||||
},
|
||||
Diagnostics: diagnostics,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cg.Name != containerGroupName {
|
||||
t.Fatalf("resource group name is %s, expected %s", cg.Name, containerGroupName)
|
||||
}
|
||||
if err := client.DeleteContainerGroup(context.Background(), resourceGroup, containerGroupName); err != nil {
|
||||
t.Fatalf("Delete Container Group failed: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteContainerGroup(t *testing.T) {
|
||||
err := client.DeleteContainerGroup(context.Background(), resourceGroup, containerGroup)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
// CreateContainerGroup creates a new Azure Container Instance with the
|
||||
// provided properties.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/container-instances/containergroups/createorupdate
|
||||
func (c *Client) CreateContainerGroup(ctx context.Context, resourceGroup, containerGroupName string, containerGroup ContainerGroup) (*ContainerGroup, error) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the body for the request.
|
||||
b := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(b).Encode(containerGroup); err != nil {
|
||||
return nil, fmt.Errorf("Encoding create container group body request failed: %v", err)
|
||||
}
|
||||
|
||||
// Create the request.
|
||||
req, err := http.NewRequest("PUT", uri, b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating create/update container group uri request failed: %v", err)
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroup": resourceGroup,
|
||||
"containerGroupName": containerGroupName,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("Expanding URL with parameters failed: %v", err)
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Sending create container group request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) and 201 (Created) are a successful responses.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode the body from the response.
|
||||
if resp.Body == nil {
|
||||
return nil, errors.New("Create container group returned an empty body in the response")
|
||||
}
|
||||
var cg ContainerGroup
|
||||
if err := json.NewDecoder(resp.Body).Decode(&cg); err != nil {
|
||||
return nil, fmt.Errorf("Decoding create container group response body failed: %v", err)
|
||||
}
|
||||
|
||||
return &cg, nil
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
// DeleteContainerGroup deletes an Azure Container Instance in the provided
|
||||
// resource group with the given container group name.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/container-instances/containergroups/delete
|
||||
func (c *Client) DeleteContainerGroup(ctx context.Context, resourceGroup, containerGroupName string) error {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
req, err := http.NewRequest("DELETE", uri, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Creating delete container group uri request failed: %v", err)
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroup": resourceGroup,
|
||||
"containerGroupName": containerGroupName,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("Expanding URL with parameters failed: %v", err)
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Sending delete container group request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// Package aci provides tools for interacting with the Azure Container Instances API.
|
||||
package aci
|
||||
@@ -1,84 +0,0 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
type TerminalSizeRequest struct {
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
// Starts the exec command for a specified container instance in a specified resource group and container group.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/container-instances/startcontainer/launchexec
|
||||
func (c *Client) LaunchExec(resourceGroup, containerGroupName, containerName, command string, terminalSize remotecommand.TerminalSize) (ExecResponse, error) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url to call Azure REST API
|
||||
uri := api.ResolveRelative(baseURI, containerExecURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
var xc ExecRequest
|
||||
|
||||
xc.Command = command
|
||||
xc.TerminalSize.Rows = int(terminalSize.Height)
|
||||
xc.TerminalSize.Cols = int(terminalSize.Width)
|
||||
|
||||
var xcrsp ExecResponse
|
||||
xcrsp.Password = ""
|
||||
xcrsp.WebSocketUri = ""
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
if err := json.NewEncoder(b).Encode(xc); err != nil {
|
||||
return xcrsp, fmt.Errorf("Encoding create launch exec body request failed: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", uri, b)
|
||||
if err != nil {
|
||||
return xcrsp, fmt.Errorf("Creating launch exec uri request failed: %v", err)
|
||||
}
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroup": resourceGroup,
|
||||
"containerGroupName": containerGroupName,
|
||||
"containerName": containerName,
|
||||
}); err != nil {
|
||||
return xcrsp, fmt.Errorf("Expanding URL with parameters failed: %v", err)
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return xcrsp, fmt.Errorf("Sending launch exec request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) is a success response.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return xcrsp, err
|
||||
}
|
||||
|
||||
// Decode the body from the response.
|
||||
if resp.Body == nil {
|
||||
return xcrsp, errors.New("Create launch exec returned an empty body in the response")
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&xcrsp); err != nil {
|
||||
return xcrsp, fmt.Errorf("Decoding create launch exec response body failed: %v", err)
|
||||
}
|
||||
|
||||
return xcrsp, nil
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
// GetContainerGroup gets an Azure Container Instance in the provided
|
||||
// resource group with the given container group name.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/container-instances/containergroups/get
|
||||
func (c *Client) GetContainerGroup(ctx context.Context, resourceGroup, containerGroupName string) (*ContainerGroup, error, *int) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
req, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating get container group uri request failed: %v", err), nil
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroup": resourceGroup,
|
||||
"containerGroupName": containerGroupName,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("Expanding URL with parameters failed: %v", err), nil
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Sending get container group request failed: %v", err), nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) is a success response.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return nil, err, &resp.StatusCode
|
||||
}
|
||||
|
||||
// Decode the body from the response.
|
||||
if resp.Body == nil {
|
||||
return nil, errors.New("Get container group returned an empty body in the response"), &resp.StatusCode
|
||||
}
|
||||
var cg ContainerGroup
|
||||
if err := json.NewDecoder(resp.Body).Decode(&cg); err != nil {
|
||||
return nil, fmt.Errorf("Decoding get container group response body failed: %v", err), &resp.StatusCode
|
||||
}
|
||||
|
||||
return &cg, nil, &resp.StatusCode
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
// ListContainerGroups lists an Azure Container Instance Groups, if a resource
|
||||
// group is given it will list by resource group.
|
||||
// It optionally accepts a resource group name and will filter based off of it
|
||||
// if it is not empty.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/container-instances/containergroups/list
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/container-instances/containergroups/listbyresourcegroup
|
||||
func (c *Client) ListContainerGroups(ctx context.Context, resourceGroup string) (*ContainerGroupListResult, error) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupListURLPath)
|
||||
// List by resource group if they passed one.
|
||||
if resourceGroup != "" {
|
||||
uri = api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupListByResourceGroupURLPath)
|
||||
|
||||
}
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
req, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating get container group list uri request failed: %v", err)
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroup": resourceGroup,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("Expanding URL with parameters failed: %v", err)
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Sending get container group list request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) is a success response.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode the body from the response.
|
||||
if resp.Body == nil {
|
||||
return nil, errors.New("Create container group list returned an empty body in the response")
|
||||
}
|
||||
var list ContainerGroupListResult
|
||||
if err := json.NewDecoder(resp.Body).Decode(&list); err != nil {
|
||||
return nil, fmt.Errorf("Decoding get container group response body failed: %v", err)
|
||||
}
|
||||
|
||||
return &list, nil
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
// GetContainerLogs returns the logs from an Azure Container Instance
|
||||
// in the provided resource group with the given container group name.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/container-instances/ContainerLogs/List
|
||||
func (c *Client) GetContainerLogs(ctx context.Context, resourceGroup, containerGroupName, containerName string, tail int) (*Logs, error) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
"tail": []string{fmt.Sprintf("%d", tail)},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerLogsURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
req, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating get container logs uri request failed: %v", err)
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroup": resourceGroup,
|
||||
"containerGroupName": containerGroupName,
|
||||
"containerName": containerName,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("Expanding URL with parameters failed: %v", err)
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Sending get container logs request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) is a success response.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode the body from the response.
|
||||
if resp.Body == nil {
|
||||
return nil, errors.New("Create container logs returned an empty body in the response")
|
||||
}
|
||||
var logs Logs
|
||||
if err := json.NewDecoder(resp.Body).Decode(&logs); err != nil {
|
||||
return nil, fmt.Errorf("Decoding get container logs response body failed: %v", err)
|
||||
}
|
||||
|
||||
return &logs, nil
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
// GetContainerGroupMetrics gets metrics for the provided container group
|
||||
func (c *Client) GetContainerGroupMetrics(ctx context.Context, resourceGroup, containerGroup string, options MetricsRequest) (*ContainerGroupMetricsResult, error) {
|
||||
if len(options.Types) == 0 {
|
||||
return nil, errors.New("must provide metrics types to fetch")
|
||||
}
|
||||
if options.Start.After(options.End) || options.Start.Equal(options.End) && !options.Start.IsZero() {
|
||||
return nil, errors.Errorf("end parameter must be after start: start=%s, end=%s", options.Start, options.End)
|
||||
}
|
||||
|
||||
var metricNames string
|
||||
for _, t := range options.Types {
|
||||
if len(metricNames) > 0 {
|
||||
metricNames += ","
|
||||
}
|
||||
metricNames += string(t)
|
||||
}
|
||||
|
||||
var ag string
|
||||
for _, a := range options.Aggregations {
|
||||
if len(ag) > 0 {
|
||||
ag += ","
|
||||
}
|
||||
ag += string(a)
|
||||
}
|
||||
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{"2018-01-01"},
|
||||
"aggregation": []string{ag},
|
||||
"metricnames": []string{metricNames},
|
||||
"interval": []string{"PT1M"}, // TODO: make configurable?
|
||||
}
|
||||
|
||||
if options.Dimension != "" {
|
||||
urlParams.Add("$filter", options.Dimension)
|
||||
}
|
||||
|
||||
if !options.Start.IsZero() || !options.End.IsZero() {
|
||||
urlParams.Add("timespan", path.Join(options.Start.Format(time.RFC3339), options.End.Format(time.RFC3339)))
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupMetricsURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
req, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating get container group metrics uri request failed")
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroup": resourceGroup,
|
||||
"containerGroupName": containerGroup,
|
||||
}); err != nil {
|
||||
return nil, errors.Wrap(err, "expanding URL with parameters failed")
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "sending get container group metrics request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) is a success response.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode the body from the response.
|
||||
if resp.Body == nil {
|
||||
return nil, errors.New("container group metrics returned an empty body in the response")
|
||||
}
|
||||
var metrics ContainerGroupMetricsResult
|
||||
if err := json.NewDecoder(resp.Body).Decode(&metrics); err != nil {
|
||||
return nil, errors.Wrap(err, "decoding get container group metrics response body failed")
|
||||
}
|
||||
|
||||
return &metrics, nil
|
||||
}
|
||||
@@ -1,490 +0,0 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
// ContainerGroupNetworkProtocol enumerates the values for container group network protocol.
|
||||
type ContainerGroupNetworkProtocol string
|
||||
|
||||
const (
|
||||
// TCP specifies the tcp state for container group network protocol.
|
||||
TCP ContainerGroupNetworkProtocol = "TCP"
|
||||
// UDP specifies the udp state for container group network protocol.
|
||||
UDP ContainerGroupNetworkProtocol = "UDP"
|
||||
)
|
||||
|
||||
// ContainerGroupRestartPolicy enumerates the values for container group restart policy.
|
||||
type ContainerGroupRestartPolicy string
|
||||
|
||||
const (
|
||||
// Always specifies the always state for container group restart policy.
|
||||
Always ContainerGroupRestartPolicy = "Always"
|
||||
// Never specifies the never state for container group restart policy.
|
||||
Never ContainerGroupRestartPolicy = "Never"
|
||||
// OnFailure specifies the on failure state for container group restart policy.
|
||||
OnFailure ContainerGroupRestartPolicy = "OnFailure"
|
||||
)
|
||||
|
||||
// ContainerNetworkProtocol enumerates the values for container network protocol.
|
||||
type ContainerNetworkProtocol string
|
||||
|
||||
const (
|
||||
// ContainerNetworkProtocolTCP specifies the container network protocol tcp state for container network protocol.
|
||||
ContainerNetworkProtocolTCP ContainerNetworkProtocol = "TCP"
|
||||
// ContainerNetworkProtocolUDP specifies the container network protocol udp state for container network protocol.
|
||||
ContainerNetworkProtocolUDP ContainerNetworkProtocol = "UDP"
|
||||
)
|
||||
|
||||
// OperatingSystemTypes enumerates the values for operating system types.
|
||||
type OperatingSystemTypes string
|
||||
|
||||
const (
|
||||
// Linux specifies the linux state for operating system types.
|
||||
Linux OperatingSystemTypes = "Linux"
|
||||
// Windows specifies the windows state for operating system types.
|
||||
Windows OperatingSystemTypes = "Windows"
|
||||
)
|
||||
|
||||
// OperationsOrigin enumerates the values for operations origin.
|
||||
type OperationsOrigin string
|
||||
|
||||
const (
|
||||
// System specifies the system state for operations origin.
|
||||
System OperationsOrigin = "System"
|
||||
// User specifies the user state for operations origin.
|
||||
User OperationsOrigin = "User"
|
||||
)
|
||||
|
||||
// AzureFileVolume is the properties of the Azure File volume. Azure File shares are mounted as volumes.
|
||||
type AzureFileVolume struct {
|
||||
ShareName string `json:"shareName,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
StorageAccountName string `json:"storageAccountName,omitempty"`
|
||||
StorageAccountKey string `json:"storageAccountKey,omitempty"`
|
||||
}
|
||||
|
||||
// Container is a container instance.
|
||||
type Container struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ContainerProperties `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerGroup is a container group.
|
||||
type ContainerGroup struct {
|
||||
api.ResponseMetadata `json:"-"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
ContainerGroupProperties `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerGroupProperties is
|
||||
type ContainerGroupProperties struct {
|
||||
ProvisioningState string `json:"provisioningState,omitempty"`
|
||||
Containers []Container `json:"containers,omitempty"`
|
||||
ImageRegistryCredentials []ImageRegistryCredential `json:"imageRegistryCredentials,omitempty"`
|
||||
RestartPolicy ContainerGroupRestartPolicy `json:"restartPolicy,omitempty"`
|
||||
IPAddress *IPAddress `json:"ipAddress,omitempty"`
|
||||
OsType OperatingSystemTypes `json:"osType,omitempty"`
|
||||
Volumes []Volume `json:"volumes,omitempty"`
|
||||
InstanceView ContainerGroupPropertiesInstanceView `json:"instanceView,omitempty"`
|
||||
Diagnostics *ContainerGroupDiagnostics `json:"diagnostics,omitempty"`
|
||||
NetworkProfile *NetworkProfileDefinition `json:"networkProfile,omitempty"`
|
||||
Extensions []*Extension `json:"extensions,omitempty"`
|
||||
DNSConfig *DNSConfig `json:"dnsConfig,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerGroupPropertiesInstanceView is the instance view of the container group. Only valid in response.
|
||||
type ContainerGroupPropertiesInstanceView struct {
|
||||
Events []Event `json:"events,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
// NetworkProfileDefinition is the network profile definition. ID should be of the form
|
||||
// /subscriptions/{subscriptionId} or /providers/{resourceProviderNamespace}/
|
||||
type NetworkProfileDefinition struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerGroupListResult is the container group list response that contains the container group properties.
|
||||
type ContainerGroupListResult struct {
|
||||
api.ResponseMetadata `json:"-"`
|
||||
Value []ContainerGroup `json:"value,omitempty"`
|
||||
NextLink string `json:"nextLink,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerPort is the port exposed on the container instance.
|
||||
type ContainerPort struct {
|
||||
Protocol ContainerNetworkProtocol `json:"protocol,omitempty"`
|
||||
Port int32 `json:"port,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerProperties is the container instance properties.
|
||||
type ContainerProperties struct {
|
||||
Image string `json:"image,omitempty"`
|
||||
Command []string `json:"command,omitempty"`
|
||||
Ports []ContainerPort `json:"ports,omitempty"`
|
||||
EnvironmentVariables []EnvironmentVariable `json:"environmentVariables,omitempty"`
|
||||
InstanceView ContainerPropertiesInstanceView `json:"instanceView,omitempty"`
|
||||
Resources ResourceRequirements `json:"resources,omitempty"`
|
||||
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
|
||||
LivenessProbe *ContainerProbe `json:"livenessProbe,omitempty"`
|
||||
ReadinessProbe *ContainerProbe `json:"readinessProbe,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerPropertiesInstanceView is the instance view of the container instance. Only valid in response.
|
||||
type ContainerPropertiesInstanceView struct {
|
||||
RestartCount int32 `json:"restartCount,omitempty"`
|
||||
CurrentState ContainerState `json:"currentState,omitempty"`
|
||||
PreviousState ContainerState `json:"previousState,omitempty"`
|
||||
Events []Event `json:"events,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerState is the container instance state.
|
||||
type ContainerState struct {
|
||||
State string `json:"state,omitempty"`
|
||||
StartTime api.JSONTime `json:"startTime,omitempty"`
|
||||
ExitCode int32 `json:"exitCode,omitempty"`
|
||||
FinishTime api.JSONTime `json:"finishTime,omitempty"`
|
||||
DetailStatus string `json:"detailStatus,omitempty"`
|
||||
}
|
||||
|
||||
// EnvironmentVariable is the environment variable to set within the container instance.
|
||||
type EnvironmentVariable struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
SecureValue string `json:"secureValue,omitempty"`
|
||||
}
|
||||
|
||||
// Event is a container group or container instance event.
|
||||
type Event struct {
|
||||
Count int32 `json:"count,omitempty"`
|
||||
FirstTimestamp api.JSONTime `json:"firstTimestamp,omitempty"`
|
||||
LastTimestamp api.JSONTime `json:"lastTimestamp,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// GitRepoVolume is represents a volume that is populated with the contents of a git repository
|
||||
type GitRepoVolume struct {
|
||||
Directory string `json:"directory,omitempty"`
|
||||
Repository string `json:"repository,omitempty"`
|
||||
Revision string `json:"revision,omitempty"`
|
||||
}
|
||||
|
||||
// ImageRegistryCredential is image registry credential.
|
||||
type ImageRegistryCredential struct {
|
||||
Server string `json:"server,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
// IPAddress is IP address for the container group.
|
||||
type IPAddress struct {
|
||||
Ports []Port `json:"ports,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
DNSNameLabel string `json:"dnsNameLabel,omitempty"`
|
||||
}
|
||||
|
||||
// Logs is the logs.
|
||||
type Logs struct {
|
||||
api.ResponseMetadata `json:"-"`
|
||||
Content string `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
// Operation is an operation for Azure Container Instance service.
|
||||
type Operation struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Display OperationDisplay `json:"display,omitempty"`
|
||||
Origin OperationsOrigin `json:"origin,omitempty"`
|
||||
}
|
||||
|
||||
// OperationDisplay is the display information of the operation.
|
||||
type OperationDisplay struct {
|
||||
Provider string `json:"provider,omitempty"`
|
||||
Resource string `json:"resource,omitempty"`
|
||||
Operation string `json:"operation,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// OperationListResult is the operation list response that contains all operations for Azure Container Instance
|
||||
// service.
|
||||
type OperationListResult struct {
|
||||
api.ResponseMetadata `json:"-"`
|
||||
Value []Operation `json:"value,omitempty"`
|
||||
NextLink string `json:"nextLink,omitempty"`
|
||||
}
|
||||
|
||||
// Port is the port exposed on the container group.
|
||||
type Port struct {
|
||||
Protocol ContainerGroupNetworkProtocol `json:"protocol,omitempty"`
|
||||
Port int32 `json:"port,omitempty"`
|
||||
}
|
||||
|
||||
// Resource is the Resource model definition.
|
||||
type Resource struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceLimits is the resource limits.
|
||||
type ResourceLimits struct {
|
||||
MemoryInGB float64 `json:"memoryInGB,omitempty"`
|
||||
CPU float64 `json:"cpu,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceRequests is the resource requests.
|
||||
type ResourceRequests struct {
|
||||
MemoryInGB float64 `json:"memoryInGB,omitempty"`
|
||||
CPU float64 `json:"cpu,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceRequirements is the resource requirements.
|
||||
type ResourceRequirements struct {
|
||||
Requests *ResourceRequests `json:"requests,omitempty"`
|
||||
Limits *ResourceLimits `json:"limits,omitempty"`
|
||||
}
|
||||
|
||||
// Usage is a single usage result
|
||||
type Usage struct {
|
||||
Unit string `json:"unit,omitempty"`
|
||||
CurrentValue int32 `json:"currentValue,omitempty"`
|
||||
Limit int32 `json:"limit,omitempty"`
|
||||
Name UsageName `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// UsageName is the name object of the resource
|
||||
type UsageName struct {
|
||||
Value string `json:"value,omitempty"`
|
||||
LocalizedValue string `json:"localizedValue,omitempty"`
|
||||
}
|
||||
|
||||
// UsageListResult is the response containing the usage data
|
||||
type UsageListResult struct {
|
||||
api.ResponseMetadata `json:"-"`
|
||||
Value []Usage `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Volume is the properties of the volume.
|
||||
type Volume struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
AzureFile *AzureFileVolume `json:"azureFile,omitempty"`
|
||||
EmptyDir map[string]interface{} `json:"emptyDir"`
|
||||
Secret map[string]string `json:"secret,omitempty"`
|
||||
GitRepo *GitRepoVolume `json:"gitRepo,omitempty"`
|
||||
}
|
||||
|
||||
// VolumeMount is the properties of the volume mount.
|
||||
type VolumeMount struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
MountPath string `json:"mountPath,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
}
|
||||
|
||||
// TerminalSize is the size of the Launch Exec terminal
|
||||
type TerminalSize struct {
|
||||
Rows int `json:"rows,omitempty"`
|
||||
Cols int `json:"cols,omitempty"`
|
||||
}
|
||||
|
||||
// ExecRequest is a request for Launch Exec API response for ACI.
|
||||
type ExecRequest struct {
|
||||
Command string `json:"command,omitempty"`
|
||||
TerminalSize TerminalSize `json:"terminalSize,omitempty"`
|
||||
}
|
||||
|
||||
// ExecResponse is a request for Launch Exec API response for ACI.
|
||||
type ExecResponse struct {
|
||||
WebSocketUri string `json:"webSocketUri,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerProbe is a probe definition that can be used for Liveness
|
||||
// or Readiness checks.
|
||||
type ContainerProbe struct {
|
||||
Exec *ContainerExecProbe `json:"exec,omitempty"`
|
||||
HTTPGet *ContainerHTTPGetProbe `json:"httpGet,omitempty"`
|
||||
InitialDelaySeconds int32 `json:"initialDelaySeconds,omitempty"`
|
||||
Period int32 `json:"periodSeconds,omitempty"`
|
||||
FailureThreshold int32 `json:"failureThreshold,omitempty"`
|
||||
SuccessThreshold int32 `json:"successThreshold,omitempty"`
|
||||
TimeoutSeconds int32 `json:"timeoutSeconds,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerExecProbe defines a command based probe
|
||||
type ContainerExecProbe struct {
|
||||
Command []string `json:"command,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerHTTPGetProbe defines an HTTP probe
|
||||
type ContainerHTTPGetProbe struct {
|
||||
Port int `json:"port"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Scheme string `json:"scheme,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerGroupDiagnostics contains an instance of LogAnalyticsWorkspace
|
||||
type ContainerGroupDiagnostics struct {
|
||||
LogAnalytics *LogAnalyticsWorkspace `json:"loganalytics,omitempty"`
|
||||
}
|
||||
|
||||
// LogAnalyticsWorkspace defines details for a Log Analytics workspace
|
||||
type LogAnalyticsWorkspace struct {
|
||||
WorkspaceID string `json:"workspaceID,omitempty"`
|
||||
WorkspaceKey string `json:"workspaceKey,omitempty"`
|
||||
LogType LogAnalyticsLogType `json:"logType,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerGroupMetricsResult stores all the results for a container group metrics request.
|
||||
type ContainerGroupMetricsResult struct {
|
||||
Value []MetricValue `json:"value"`
|
||||
}
|
||||
|
||||
// MetricValue stores metrics results
|
||||
type MetricValue struct {
|
||||
ID string `json:"id"`
|
||||
Desc MetricDescriptor `json:"name"`
|
||||
Timeseries []MetricTimeSeries `json:"timeseries"`
|
||||
Type string `json:"type"`
|
||||
Unit string `json:"unit"`
|
||||
}
|
||||
|
||||
// MetricDescriptor stores the name for a given metric and the localized version of that name.
|
||||
type MetricDescriptor struct {
|
||||
Value MetricType `json:"value"`
|
||||
LocalizedValue string `json:"localizedValue"`
|
||||
}
|
||||
|
||||
// MetricTimeSeries is the time series for a given metric
|
||||
// It contains all the metrics values and other details for the dimension the metrics are aggregated on.
|
||||
type MetricTimeSeries struct {
|
||||
Data []TimeSeriesEntry `json:"data"`
|
||||
MetadataValues []MetricMetadataValue `json:"metadatavalues,omitempty"`
|
||||
}
|
||||
|
||||
// MetricMetadataValue stores extra metadata about a metric
|
||||
// In particular it is used to provide details about the breakdown of a metric dimension.
|
||||
type MetricMetadataValue struct {
|
||||
Name ValueDescriptor `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// ValueDescriptor describes a generic value.
|
||||
// It is used to describe metadata fields.
|
||||
type ValueDescriptor struct {
|
||||
Value string `json:"value"`
|
||||
LocalizedValue string `json:"localizedValue"`
|
||||
}
|
||||
|
||||
// TimeSeriesEntry is the metric data for a given timestamp/metric type
|
||||
type TimeSeriesEntry struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Average float64 `json:"average"`
|
||||
Total float64 `json:"total"`
|
||||
Count float64 `json:"count"`
|
||||
}
|
||||
|
||||
// MetricsRequest is an options struct used when getting container group metrics
|
||||
type MetricsRequest struct {
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Types []MetricType
|
||||
Aggregations []AggregationType
|
||||
|
||||
// Note that a dimension may not be available for certain metrics.
|
||||
// In such cases, you will need to make separate requests.
|
||||
Dimension string
|
||||
}
|
||||
|
||||
// MetricType is an enum type for defining supported metric types.
|
||||
type MetricType string
|
||||
|
||||
// Supported metric types
|
||||
const (
|
||||
MetricTypeCPUUsage MetricType = "CpuUsage"
|
||||
MetricTypeMemoryUsage MetricType = "MemoryUsage"
|
||||
MetricTyperNetworkBytesRecievedPerSecond MetricType = "NetworkBytesReceivedPerSecond"
|
||||
MetricTyperNetworkBytesTransmittedPerSecond MetricType = "NetworkBytesTransmittedPerSecond"
|
||||
)
|
||||
|
||||
// AggregationType is an enum type for defining supported aggregation types
|
||||
type AggregationType string
|
||||
|
||||
// Supported metric aggregation types
|
||||
const (
|
||||
AggregationTypeCount AggregationType = "count"
|
||||
AggregationTypeAverage AggregationType = "average"
|
||||
AggregationTypeTotal AggregationType = "total"
|
||||
)
|
||||
|
||||
// Extension is the container group extension
|
||||
type Extension struct {
|
||||
Name string `json:"name"`
|
||||
Properties *ExtensionProperties `json:"properties"`
|
||||
}
|
||||
|
||||
// ExtensionProperties is the properties for extension
|
||||
type ExtensionProperties struct {
|
||||
Type ExtensionType `json:"extensionType"`
|
||||
Version ExtensionVersion `json:"version"`
|
||||
Settings map[string]string `json:"settings,omitempty"`
|
||||
ProtectedSettings map[string]string `json:"protectedSettings,omitempty"`
|
||||
}
|
||||
|
||||
// ExtensionType is an enum type for defining supported extension types
|
||||
type ExtensionType string
|
||||
|
||||
// Supported extension types
|
||||
const (
|
||||
ExtensionTypeKubeProxy ExtensionType = "kube-proxy"
|
||||
)
|
||||
|
||||
// ExtensionVersion is an enum type for defining supported extension versions
|
||||
type ExtensionVersion string
|
||||
|
||||
// Supported extension version
|
||||
const (
|
||||
ExtensionVersion1_0 ExtensionVersion = "1.0"
|
||||
)
|
||||
|
||||
// Supported kube-proxy extension constants
|
||||
const (
|
||||
KubeProxyExtensionSettingClusterCIDR string = "clusterCidr"
|
||||
KubeProxyExtensionSettingKubeVersion string = "kubeVersion"
|
||||
KubeProxyExtensionSettingKubeConfig string = "kubeConfig"
|
||||
KubeProxyExtensionKubeVersion string = "v1.9.10"
|
||||
)
|
||||
|
||||
// DNSConfig is the DNS config for container group
|
||||
type DNSConfig struct {
|
||||
NameServers []string `json:"nameServers"`
|
||||
SearchDomains string `json:"searchDomains,omitempty"`
|
||||
Options string `json:"options,omitempty"`
|
||||
}
|
||||
|
||||
// LogAnalyticsLogType is an enum type for defining supported log analytics log types
|
||||
type LogAnalyticsLogType string
|
||||
|
||||
// Supported log analytics log types
|
||||
const (
|
||||
LogAnlyticsLogTypeContainerInsights LogAnalyticsLogType = "ContainerInsights"
|
||||
LogAnlyticsLogTypeContainerInstance LogAnalyticsLogType = "ContainerInstance"
|
||||
)
|
||||
|
||||
// Supported log analytics metadata keys
|
||||
const (
|
||||
LogAnalyticsMetadataKeyPodUUID string = "pod-uuid"
|
||||
LogAnalyticsMetadataKeyNodeName string = "node-name"
|
||||
LogAnalyticsMetadataKeyClusterResourceID string = "cluster-resource-id"
|
||||
)
|
||||
@@ -1,10 +0,0 @@
|
||||
package aci
|
||||
|
||||
import "context"
|
||||
|
||||
// UpdateContainerGroup updates an Azure Container Instance with the
|
||||
// provided properties.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/container-instances/containergroups/createorupdate
|
||||
func (c *Client) UpdateContainerGroup(ctx context.Context, resourceGroup, containerGroupName string, containerGroup ContainerGroup) (*ContainerGroup, error) {
|
||||
return c.CreateContainerGroup(ctx, resourceGroup, containerGroupName, containerGroup)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// Package api contains the common code shared by all Azure API libraries.
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Error contains an error response from the server.
|
||||
type Error struct {
|
||||
// StatusCode is the HTTP response status code and will always be populated.
|
||||
StatusCode int `json:"statusCode"`
|
||||
// Code is the API error code that is given in the error message.
|
||||
Code string `json:"code"`
|
||||
// Message is the server response message and is only populated when
|
||||
// explicitly referenced by the JSON server response.
|
||||
Message string `json:"message"`
|
||||
// Body is the raw response returned by the server.
|
||||
// It is often but not always JSON, depending on how the request fails.
|
||||
Body string
|
||||
// Header contains the response header fields from the server.
|
||||
Header http.Header
|
||||
// URL is the URL of the original HTTP request and will always be populated.
|
||||
URL string
|
||||
}
|
||||
|
||||
// Error converts the Error type to a readable string.
|
||||
func (e *Error) Error() string {
|
||||
// If the message is empty return early.
|
||||
if e.Message == "" {
|
||||
return fmt.Sprintf("api call to %s: got HTTP response status code %d error code %q with body: %v", e.URL, e.StatusCode, e.Code, e.Body)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("api call to %s: got HTTP response status code %d error code %q: %s", e.URL, e.StatusCode, e.Code, e.Message)
|
||||
}
|
||||
|
||||
type errorReply struct {
|
||||
Error *Error `json:"error"`
|
||||
}
|
||||
|
||||
// CheckResponse returns an error (of type *Error) if the response
|
||||
// status code is not 2xx.
|
||||
func CheckResponse(res *http.Response) error {
|
||||
if res.StatusCode >= 200 && res.StatusCode <= 299 {
|
||||
return nil
|
||||
}
|
||||
|
||||
slurp, err := ioutil.ReadAll(res.Body)
|
||||
if err == nil {
|
||||
jerr := new(errorReply)
|
||||
err = json.Unmarshal(slurp, jerr)
|
||||
if err == nil && jerr.Error != nil {
|
||||
if jerr.Error.StatusCode == 0 {
|
||||
jerr.Error.StatusCode = res.StatusCode
|
||||
}
|
||||
jerr.Error.Body = string(slurp)
|
||||
jerr.Error.URL = res.Request.URL.String()
|
||||
return jerr.Error
|
||||
}
|
||||
}
|
||||
|
||||
return &Error{
|
||||
StatusCode: res.StatusCode,
|
||||
Body: res.Status,
|
||||
Header: res.Header,
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ResponseMetadata is embedded in each response and contains the HTTP response code and headers from the server.
|
||||
type ResponseMetadata struct {
|
||||
// HTTPStatusCode is the server's response status code.
|
||||
HTTPStatusCode int
|
||||
// Header contains the response header fields from the server.
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
// JSONTime assumes the time format is RFC3339.
|
||||
type JSONTime time.Time
|
||||
|
||||
const AzureTimeFormat = "2006-01-02T15:04:05Z"
|
||||
|
||||
// MarshalJSON ensures that the time is serialized as RFC3339.
|
||||
func (t JSONTime) MarshalJSON() ([]byte, error) {
|
||||
// Serialize the JSON as RFC3339.
|
||||
stamp := fmt.Sprintf("\"%s\"", time.Time(t).Format(AzureTimeFormat))
|
||||
return []byte(stamp), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON ensures that the time is deserialized as RFC3339.
|
||||
func (t *JSONTime) UnmarshalJSON(data []byte) error {
|
||||
if t == nil {
|
||||
return errors.New("api.JSONTime: UnmarshalJSON on nil pointer")
|
||||
}
|
||||
|
||||
parsed, err := time.Parse(AzureTimeFormat, string(bytes.Trim(data, "\"")))
|
||||
if err != nil {
|
||||
return fmt.Errorf("api.JSONTime: UnmarshalJSON failed: %v", err)
|
||||
}
|
||||
*t = JSONTime(parsed)
|
||||
return nil
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// ResolveRelative combines a url base with a relative path.
|
||||
func ResolveRelative(basestr, relstr string) string {
|
||||
u, _ := url.Parse(basestr)
|
||||
rel, _ := url.Parse(relstr)
|
||||
u = u.ResolveReference(rel)
|
||||
us := u.String()
|
||||
us = strings.Replace(us, "%7B", "{", -1)
|
||||
us = strings.Replace(us, "%7D", "}", -1)
|
||||
return us
|
||||
}
|
||||
|
||||
// ExpandURL subsitutes any {{encoded}} strings in the URL passed in using
|
||||
// the map supplied.
|
||||
func ExpandURL(u *url.URL, expansions map[string]string) error {
|
||||
t, err := template.New("url").Parse(u.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Parsing template for url path %q failed: %v", u.Path, err)
|
||||
}
|
||||
var b bytes.Buffer
|
||||
if err := t.Execute(&b, expansions); err != nil {
|
||||
return fmt.Errorf("Executing template for url path failed: %v", err)
|
||||
}
|
||||
|
||||
// set the parameters
|
||||
u.Path = b.String()
|
||||
|
||||
// escape the expansions
|
||||
for k, v := range expansions {
|
||||
expansions[k] = url.QueryEscape(v)
|
||||
}
|
||||
|
||||
var bt bytes.Buffer
|
||||
if err := t.Execute(&bt, expansions); err != nil {
|
||||
return fmt.Errorf("Executing template for url path failed: %v", err)
|
||||
}
|
||||
|
||||
// set the parameters
|
||||
u.RawPath = bt.String()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURI = "https://management.azure.com"
|
||||
)
|
||||
|
||||
type expandTest struct {
|
||||
in string
|
||||
expansions map[string]string
|
||||
want string
|
||||
}
|
||||
|
||||
var expandTests = []expandTest{
|
||||
// no expansions
|
||||
{
|
||||
"",
|
||||
map[string]string{},
|
||||
"https://management.azure.com",
|
||||
},
|
||||
// multiple expansions, no escaping
|
||||
{
|
||||
"subscriptions/{{.subscriptionId}}/resourceGroups/{{.resourceGroup}}/providers/Microsoft.ContainerInstance/containerGroups/{{.containerGroupName}}",
|
||||
map[string]string{
|
||||
"subscriptionId": "foo",
|
||||
"resourceGroup": "bar",
|
||||
"containerGroupName": "baz",
|
||||
},
|
||||
"https://management.azure.com/subscriptions/foo/resourceGroups/bar/providers/Microsoft.ContainerInstance/containerGroups/baz",
|
||||
},
|
||||
// one expansion, with hex escapes
|
||||
{
|
||||
"subscriptions/{{.subscriptionId}}/resourceGroups/{{.resourceGroup}}/providers/Microsoft.ContainerInstance/containerGroups/{{.containerGroupName}}",
|
||||
map[string]string{
|
||||
"subscriptionId": "foo/bar",
|
||||
"resourceGroup": "bar",
|
||||
"containerGroupName": "baz",
|
||||
},
|
||||
"https://management.azure.com/subscriptions/foo%2Fbar/resourceGroups/bar/providers/Microsoft.ContainerInstance/containerGroups/baz",
|
||||
},
|
||||
// one expansion, with space
|
||||
{
|
||||
"subscriptions/{{.subscriptionId}}/resourceGroups/{{.resourceGroup}}/providers/Microsoft.ContainerInstance/containerGroups/{{.containerGroupName}}",
|
||||
map[string]string{
|
||||
"subscriptionId": "foo and bar",
|
||||
"resourceGroup": "bar",
|
||||
"containerGroupName": "baz",
|
||||
},
|
||||
"https://management.azure.com/subscriptions/foo%20and%20bar/resourceGroups/bar/providers/Microsoft.ContainerInstance/containerGroups/baz",
|
||||
},
|
||||
// expansion not found
|
||||
{
|
||||
"subscriptions/{{.subscriptionId}}/resourceGroups/{{.resourceGroup}}/providers/Microsoft.ContainerInstance/containerGroups/{{.containerGroupName}}",
|
||||
map[string]string{
|
||||
"subscriptionId": "foo",
|
||||
"containerGroupName": "baz",
|
||||
},
|
||||
"https://management.azure.com/subscriptions/foo/resourceGroups/%3Cno%20value%3E/providers/Microsoft.ContainerInstance/containerGroups/baz",
|
||||
},
|
||||
// utf-8 characters
|
||||
{
|
||||
"{{.bucket}}/get",
|
||||
map[string]string{
|
||||
"bucket": "£100",
|
||||
},
|
||||
"https://management.azure.com/%C2%A3100/get",
|
||||
},
|
||||
// punctuations
|
||||
{
|
||||
"{{.bucket}}/get",
|
||||
map[string]string{
|
||||
"bucket": `/\@:,.`,
|
||||
},
|
||||
"https://management.azure.com/%2F%5C%40%3A%2C./get",
|
||||
},
|
||||
// mis-matched brackets
|
||||
{
|
||||
"/{{.bucket/get",
|
||||
map[string]string{
|
||||
"bucket": "red",
|
||||
},
|
||||
"https://management.azure.com/%7B%7B.bucket/get",
|
||||
},
|
||||
}
|
||||
|
||||
func TestExpandURL(t *testing.T) {
|
||||
for i, test := range expandTests {
|
||||
uri := ResolveRelative(baseURI, test.in)
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
t.Fatalf("Parsing url %q failed: %v", test.in, err)
|
||||
}
|
||||
ExpandURL(u, test.expansions)
|
||||
got := u.String()
|
||||
if got != test.want {
|
||||
t.Errorf("got %q expected %q in test %d", got, test.want, i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/dimchansky/utfbom"
|
||||
)
|
||||
|
||||
// Authentication represents the authentication file for Azure.
|
||||
type Authentication struct {
|
||||
ClientID string `json:"clientId,omitempty"`
|
||||
ClientSecret string `json:"clientSecret,omitempty"`
|
||||
SubscriptionID string `json:"subscriptionId,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
ActiveDirectoryEndpoint string `json:"activeDirectoryEndpointUrl,omitempty"`
|
||||
ResourceManagerEndpoint string `json:"resourceManagerEndpointUrl,omitempty"`
|
||||
GraphResourceID string `json:"activeDirectoryGraphResourceId,omitempty"`
|
||||
SQLManagementEndpoint string `json:"sqlManagementEndpointUrl,omitempty"`
|
||||
GalleryEndpoint string `json:"galleryEndpointUrl,omitempty"`
|
||||
ManagementEndpoint string `json:"managementEndpointUrl,omitempty"`
|
||||
}
|
||||
|
||||
// NewAuthentication returns an authentication struct from user provided
|
||||
// credentials.
|
||||
func NewAuthentication(azureCloud, clientID, clientSecret, subscriptionID, tenantID string) *Authentication {
|
||||
environment := PublicCloud
|
||||
|
||||
switch azureCloud {
|
||||
case PublicCloud.Name:
|
||||
environment = PublicCloud
|
||||
break
|
||||
case USGovernmentCloud.Name:
|
||||
environment = USGovernmentCloud
|
||||
break
|
||||
case ChinaCloud.Name:
|
||||
environment = ChinaCloud
|
||||
break
|
||||
case GermanCloud.Name:
|
||||
environment = GermanCloud
|
||||
break
|
||||
}
|
||||
|
||||
return &Authentication{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
SubscriptionID: subscriptionID,
|
||||
TenantID: tenantID,
|
||||
ActiveDirectoryEndpoint: environment.ActiveDirectoryEndpoint,
|
||||
ResourceManagerEndpoint: environment.ResourceManagerEndpoint,
|
||||
GraphResourceID: environment.GraphEndpoint,
|
||||
SQLManagementEndpoint: environment.SQLDatabaseDNSSuffix,
|
||||
GalleryEndpoint: environment.GalleryEndpoint,
|
||||
ManagementEndpoint: environment.ServiceManagementEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// NewAuthenticationFromFile returns an authentication struct from file path
|
||||
func NewAuthenticationFromFile(filepath string) (*Authentication, error) {
|
||||
b, err := ioutil.ReadFile(filepath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Reading authentication file %q failed: %v", filepath, err)
|
||||
}
|
||||
|
||||
// Authentication file might be encoded.
|
||||
decoded, err := decode(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Decoding authentication file %q failed: %v", filepath, err)
|
||||
}
|
||||
|
||||
// Unmarshal the authentication file.
|
||||
var auth Authentication
|
||||
if err := json.Unmarshal(decoded, &auth); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth, nil
|
||||
|
||||
}
|
||||
|
||||
func decode(b []byte) ([]byte, error) {
|
||||
reader, enc := utfbom.Skip(bytes.NewReader(b))
|
||||
|
||||
switch enc {
|
||||
case utfbom.UTF16LittleEndian:
|
||||
u16 := make([]uint16, (len(b)/2)-1)
|
||||
err := binary.Read(reader, binary.LittleEndian, &u16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(string(utf16.Decode(u16))), nil
|
||||
case utfbom.UTF16BigEndian:
|
||||
u16 := make([]uint16, (len(b)/2)-1)
|
||||
err := binary.Read(reader, binary.BigEndian, &u16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(string(utf16.Decode(u16))), nil
|
||||
}
|
||||
return ioutil.ReadAll(reader)
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
)
|
||||
|
||||
// Client represents authentication details and cloud specific parameters for
|
||||
// Azure Resource Manager clients.
|
||||
type Client struct {
|
||||
Authentication *Authentication
|
||||
BaseURI string
|
||||
HTTPClient *http.Client
|
||||
BearerAuthorizer *BearerAuthorizer
|
||||
}
|
||||
|
||||
// BearerAuthorizer implements the bearer authorization.
|
||||
type BearerAuthorizer struct {
|
||||
tokenProvider adal.OAuthTokenProvider
|
||||
}
|
||||
|
||||
type userAgentTransport struct {
|
||||
userAgent []string
|
||||
base http.RoundTripper
|
||||
client *Client
|
||||
}
|
||||
|
||||
// NewClient creates a new Azure API client from an Authentication struct and BaseURI.
|
||||
func NewClient(auth *Authentication, baseURI string, userAgent []string) (*Client, error) {
|
||||
resource, err := getResourceForToken(auth, baseURI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Getting resource for token failed: %v", err)
|
||||
}
|
||||
client := &Client{
|
||||
Authentication: auth,
|
||||
BaseURI: resource,
|
||||
}
|
||||
|
||||
config, err := adal.NewOAuthConfig(auth.ActiveDirectoryEndpoint, auth.TenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating new OAuth config for active directory failed: %v", err)
|
||||
}
|
||||
|
||||
tp, err := adal.NewServicePrincipalToken(*config, auth.ClientID, auth.ClientSecret, resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating new service principal token failed: %v", err)
|
||||
}
|
||||
|
||||
client.BearerAuthorizer = &BearerAuthorizer{tokenProvider: tp}
|
||||
|
||||
nonEmptyUserAgent := userAgent[:0]
|
||||
for _, ua := range userAgent {
|
||||
if ua != "" {
|
||||
nonEmptyUserAgent = append(nonEmptyUserAgent, ua)
|
||||
}
|
||||
}
|
||||
|
||||
uat := userAgentTransport{
|
||||
base: http.DefaultTransport,
|
||||
userAgent: nonEmptyUserAgent,
|
||||
client: client,
|
||||
}
|
||||
|
||||
client.HTTPClient = &http.Client{
|
||||
Transport: uat,
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (t userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if t.base == nil {
|
||||
return nil, errors.New("RoundTrip: no Transport specified")
|
||||
}
|
||||
|
||||
newReq := *req
|
||||
newReq.Header = make(http.Header)
|
||||
for k, vv := range req.Header {
|
||||
newReq.Header[k] = vv
|
||||
}
|
||||
|
||||
// Add the user agent header.
|
||||
newReq.Header["User-Agent"] = []string{strings.Join(t.userAgent, " ")}
|
||||
|
||||
// Add the content-type header.
|
||||
newReq.Header["Content-Type"] = []string{"application/json"}
|
||||
|
||||
// Refresh the token if necessary
|
||||
// TODO: don't refresh the token everytime
|
||||
refresher, ok := t.client.BearerAuthorizer.tokenProvider.(adal.Refresher)
|
||||
if ok {
|
||||
if err := refresher.EnsureFresh(); err != nil {
|
||||
return nil, fmt.Errorf("Failed to refresh the authorization token for request to %s: %v", newReq.URL, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Add the authorization header.
|
||||
newReq.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", t.client.BearerAuthorizer.tokenProvider.OAuthToken())}
|
||||
|
||||
return t.base.RoundTrip(&newReq)
|
||||
}
|
||||
|
||||
func getResourceForToken(auth *Authentication, baseURI string) (string, error) {
|
||||
// Compare dafault base URI from the SDK to the endpoints from the public cloud
|
||||
// Base URI and token resource are the same string. This func finds the authentication
|
||||
// file field that matches the SDK base URI. The SDK defines the public cloud
|
||||
// endpoint as its default base URI
|
||||
if !strings.HasSuffix(baseURI, "/") {
|
||||
baseURI += "/"
|
||||
}
|
||||
switch baseURI {
|
||||
case PublicCloud.ServiceManagementEndpoint:
|
||||
return auth.ManagementEndpoint, nil
|
||||
case PublicCloud.ResourceManagerEndpoint:
|
||||
return auth.ResourceManagerEndpoint, nil
|
||||
case PublicCloud.ActiveDirectoryEndpoint:
|
||||
return auth.ActiveDirectoryEndpoint, nil
|
||||
case PublicCloud.GalleryEndpoint:
|
||||
return auth.GalleryEndpoint, nil
|
||||
case PublicCloud.GraphEndpoint:
|
||||
return auth.GraphResourceID, nil
|
||||
}
|
||||
return "", fmt.Errorf("baseURI provided %q not found in endpoints", baseURI)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// Package azure and subpackages are used to perform operations using the
|
||||
// Azure Resource Manager (ARM).
|
||||
package azure
|
||||
@@ -1,114 +0,0 @@
|
||||
package azure
|
||||
|
||||
const (
|
||||
// EnvironmentFilepathName defines the name of the environment variable
|
||||
// containing the path to the file to be used to populate the Azure Environment.
|
||||
EnvironmentFilepathName = "AZURE_ENVIRONMENT_FILEPATH"
|
||||
)
|
||||
|
||||
// Environment represents a set of endpoints for each of Azure's Clouds.
|
||||
type Environment struct {
|
||||
Name string `json:"name"`
|
||||
ManagementPortalURL string `json:"managementPortalURL"`
|
||||
PublishSettingsURL string `json:"publishSettingsURL"`
|
||||
ServiceManagementEndpoint string `json:"serviceManagementEndpoint"`
|
||||
ResourceManagerEndpoint string `json:"resourceManagerEndpoint"`
|
||||
ActiveDirectoryEndpoint string `json:"activeDirectoryEndpoint"`
|
||||
GalleryEndpoint string `json:"galleryEndpoint"`
|
||||
KeyVaultEndpoint string `json:"keyVaultEndpoint"`
|
||||
GraphEndpoint string `json:"graphEndpoint"`
|
||||
StorageEndpointSuffix string `json:"storageEndpointSuffix"`
|
||||
SQLDatabaseDNSSuffix string `json:"sqlDatabaseDNSSuffix"`
|
||||
TrafficManagerDNSSuffix string `json:"trafficManagerDNSSuffix"`
|
||||
KeyVaultDNSSuffix string `json:"keyVaultDNSSuffix"`
|
||||
ServiceBusEndpointSuffix string `json:"serviceBusEndpointSuffix"`
|
||||
ServiceManagementVMDNSSuffix string `json:"serviceManagementVMDNSSuffix"`
|
||||
ResourceManagerVMDNSSuffix string `json:"resourceManagerVMDNSSuffix"`
|
||||
ContainerRegistryDNSSuffix string `json:"containerRegistryDNSSuffix"`
|
||||
}
|
||||
|
||||
var (
|
||||
// PublicCloud is the default public Azure cloud environment.
|
||||
PublicCloud = Environment{
|
||||
Name: "AzurePublicCloud",
|
||||
ManagementPortalURL: "https://manage.windowsazure.com/",
|
||||
PublishSettingsURL: "https://manage.windowsazure.com/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.windows.net/",
|
||||
ResourceManagerEndpoint: "https://management.azure.com/",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
|
||||
GalleryEndpoint: "https://gallery.azure.com/",
|
||||
KeyVaultEndpoint: "https://vault.azure.net/",
|
||||
GraphEndpoint: "https://graph.windows.net/",
|
||||
StorageEndpointSuffix: "core.windows.net",
|
||||
SQLDatabaseDNSSuffix: "database.windows.net",
|
||||
TrafficManagerDNSSuffix: "trafficmanager.net",
|
||||
KeyVaultDNSSuffix: "vault.azure.net",
|
||||
ServiceBusEndpointSuffix: "servicebus.azure.com",
|
||||
ServiceManagementVMDNSSuffix: "cloudapp.net",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.azure.com",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
}
|
||||
|
||||
// USGovernmentCloud is the cloud environment for the US Government.
|
||||
USGovernmentCloud = Environment{
|
||||
Name: "AzureUSGovernmentCloud",
|
||||
ManagementPortalURL: "https://manage.windowsazure.us/",
|
||||
PublishSettingsURL: "https://manage.windowsazure.us/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.usgovcloudapi.net/",
|
||||
ResourceManagerEndpoint: "https://management.usgovcloudapi.net/",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
|
||||
GalleryEndpoint: "https://gallery.usgovcloudapi.net/",
|
||||
KeyVaultEndpoint: "https://vault.usgovcloudapi.net/",
|
||||
GraphEndpoint: "https://graph.usgovcloudapi.net/",
|
||||
StorageEndpointSuffix: "core.usgovcloudapi.net",
|
||||
SQLDatabaseDNSSuffix: "database.usgovcloudapi.net",
|
||||
TrafficManagerDNSSuffix: "usgovtrafficmanager.net",
|
||||
KeyVaultDNSSuffix: "vault.usgovcloudapi.net",
|
||||
ServiceBusEndpointSuffix: "servicebus.usgovcloudapi.net",
|
||||
ServiceManagementVMDNSSuffix: "usgovcloudapp.net",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.windowsazure.us",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
}
|
||||
|
||||
// ChinaCloud is the cloud environment operated in China.
|
||||
ChinaCloud = Environment{
|
||||
Name: "AzureChinaCloud",
|
||||
ManagementPortalURL: "https://manage.chinacloudapi.com/",
|
||||
PublishSettingsURL: "https://manage.chinacloudapi.com/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.chinacloudapi.cn/",
|
||||
ResourceManagerEndpoint: "https://management.chinacloudapi.cn/",
|
||||
ActiveDirectoryEndpoint: "https://login.chinacloudapi.cn/",
|
||||
GalleryEndpoint: "https://gallery.chinacloudapi.cn/",
|
||||
KeyVaultEndpoint: "https://vault.azure.cn/",
|
||||
GraphEndpoint: "https://graph.chinacloudapi.cn/",
|
||||
StorageEndpointSuffix: "core.chinacloudapi.cn",
|
||||
SQLDatabaseDNSSuffix: "database.chinacloudapi.cn",
|
||||
TrafficManagerDNSSuffix: "trafficmanager.cn",
|
||||
KeyVaultDNSSuffix: "vault.azure.cn",
|
||||
ServiceBusEndpointSuffix: "servicebus.chinacloudapi.net",
|
||||
ServiceManagementVMDNSSuffix: "chinacloudapp.cn",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.azure.cn",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
}
|
||||
|
||||
// GermanCloud is the cloud environment operated in Germany.
|
||||
GermanCloud = Environment{
|
||||
Name: "AzureGermanCloud",
|
||||
ManagementPortalURL: "http://portal.microsoftazure.de/",
|
||||
PublishSettingsURL: "https://manage.microsoftazure.de/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.cloudapi.de/",
|
||||
ResourceManagerEndpoint: "https://management.microsoftazure.de/",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.de/",
|
||||
GalleryEndpoint: "https://gallery.cloudapi.de/",
|
||||
KeyVaultEndpoint: "https://vault.microsoftazure.de/",
|
||||
GraphEndpoint: "https://graph.cloudapi.de/",
|
||||
StorageEndpointSuffix: "core.cloudapi.de",
|
||||
SQLDatabaseDNSSuffix: "database.cloudapi.de",
|
||||
TrafficManagerDNSSuffix: "azuretrafficmanager.de",
|
||||
KeyVaultDNSSuffix: "vault.microsoftazure.de",
|
||||
ServiceBusEndpointSuffix: "servicebus.cloudapi.de",
|
||||
ServiceManagementVMDNSSuffix: "azurecloudapp.de",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.microsoftazure.de",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
}
|
||||
)
|
||||
@@ -1,68 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-08-01/network"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
azure "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURI = "https://management.azure.com"
|
||||
defaultUserAgent = "virtual-kubelet/azure-arm-network/2018-08-01"
|
||||
apiVersion = "2018-08-01"
|
||||
)
|
||||
|
||||
// Client is a client for interacting with Azure networking
|
||||
type Client struct {
|
||||
sc network.SubnetsClient
|
||||
hc *http.Client
|
||||
|
||||
auth *azure.Authentication
|
||||
}
|
||||
|
||||
// NewClient creates a new client for interacting with azure networking
|
||||
func NewClient(azAuth *azure.Authentication, extraUserAgent string) (*Client, error) {
|
||||
if azAuth == nil {
|
||||
return nil, fmt.Errorf("Authentication is not supplied for the Azure client")
|
||||
}
|
||||
|
||||
userAgent := []string{defaultUserAgent}
|
||||
if extraUserAgent != "" {
|
||||
userAgent = append(userAgent, extraUserAgent)
|
||||
}
|
||||
|
||||
client, err := azure.NewClient(azAuth, baseURI, userAgent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating Azure client failed: %v", err)
|
||||
}
|
||||
|
||||
authorizer, err := auth.NewClientCredentialsConfig(azAuth.ClientID, azAuth.ClientSecret, azAuth.TenantID).Authorizer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sc := network.NewSubnetsClient(azAuth.SubscriptionID)
|
||||
sc.Authorizer = authorizer
|
||||
|
||||
return &Client{
|
||||
sc: sc,
|
||||
hc: client.HTTPClient,
|
||||
auth: azAuth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsNotFound determines if the passed in error is a not found error from the API.
|
||||
func IsNotFound(err error) bool {
|
||||
switch e := err.(type) {
|
||||
case nil:
|
||||
return false
|
||||
case *api.Error:
|
||||
return e.StatusCode == http.StatusNotFound
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-08-01/network"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
)
|
||||
|
||||
var vnetAuthOnce sync.Once
|
||||
var azAuth autorest.Authorizer
|
||||
|
||||
func ensureVnet(t *testing.T, name string) {
|
||||
vnetAuthOnce.Do(func() {
|
||||
var err error
|
||||
azAuth, err = auth.NewClientCredentialsConfig(testAuth.ClientID, testAuth.ClientSecret, testAuth.TenantID).Authorizer()
|
||||
if err != nil {
|
||||
t.Fatalf("error setting up client auth for vnet create: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
client := network.NewVirtualNetworksClient(testAuth.SubscriptionID)
|
||||
client.Authorizer = azAuth
|
||||
|
||||
prefixes := []string{"10.0.0.0/24"}
|
||||
result, err := client.CreateOrUpdate(context.Background(), resourceGroup, name, network.VirtualNetwork{
|
||||
Name: &name,
|
||||
Location: &location,
|
||||
VirtualNetworkPropertiesFormat: &network.VirtualNetworkPropertiesFormat{
|
||||
AddressSpace: &network.AddressSpace{
|
||||
AddressPrefixes: &prefixes,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := result.WaitForCompletion(context.Background(), client.Client); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
azure "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/resourcegroups"
|
||||
)
|
||||
|
||||
var (
|
||||
location = "westus"
|
||||
resourceGroup = "virtual-kubelet-tests"
|
||||
testAuth *azure.Authentication
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
uid := uuid.New()
|
||||
resourceGroup += "-" + uid.String()[0:6]
|
||||
|
||||
if err := setupAuth(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error setting up auth:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
c, err := resourcegroups.NewClient(testAuth, "unit-test")
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
_, err = c.CreateResourceGroup(resourceGroup, resourcegroups.Group{
|
||||
Name: resourceGroup,
|
||||
Location: location,
|
||||
})
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
|
||||
if err := c.DeleteResourceGroup(resourceGroup); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error removing resource group:", err)
|
||||
}
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
var authOnce sync.Once
|
||||
|
||||
func setupAuth() error {
|
||||
var err error
|
||||
authOnce.Do(func() {
|
||||
testAuth, err = azure.NewAuthenticationFromFile(os.Getenv("AZURE_AUTH_LOCATION"))
|
||||
if err != nil {
|
||||
testAuth, err = azure.NewAuthenticationFromFile("../../../../credentials.json")
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "failed to load Azure authentication file")
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func newTestClient(t *testing.T) *Client {
|
||||
if err := setupAuth(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, err := NewClient(testAuth, "unit-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-08-01/network"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
const (
|
||||
profilePath = "subscriptions/{{.subscriptionId}}/resourcegroups/{{.resourceGroupName}}/providers/Microsoft.Network/networkProfiles/{{.profileName}}"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultNicName = "eth0"
|
||||
defaultIPConfigName = "ipconfigprofile1"
|
||||
)
|
||||
|
||||
// NewNetworkProfile creates a new instance of network profile
|
||||
func NewNetworkProfile(name, location, subnetID string ) *network.Profile {
|
||||
p := network.Profile{
|
||||
Name: &name,
|
||||
Location: &location,
|
||||
ProfilePropertiesFormat: &network.ProfilePropertiesFormat{
|
||||
ContainerNetworkInterfaceConfigurations: &[]network.ContainerNetworkInterfaceConfiguration{
|
||||
network.ContainerNetworkInterfaceConfiguration{
|
||||
Name: &defaultNicName,
|
||||
ContainerNetworkInterfaceConfigurationPropertiesFormat: &network.ContainerNetworkInterfaceConfigurationPropertiesFormat{
|
||||
IPConfigurations: &[]network.IPConfigurationProfile{
|
||||
network.IPConfigurationProfile{
|
||||
Name: &defaultIPConfigName,
|
||||
IPConfigurationProfilePropertiesFormat: &network.IPConfigurationProfilePropertiesFormat{
|
||||
Subnet: &network.Subnet{ID: &subnetID},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
// GetProfile gets the network profile with the provided name
|
||||
func (c *Client) GetProfile(resourceGroup, name string) (*network.Profile, error) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(baseURI, profilePath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
req, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating network profile get uri request failed")
|
||||
}
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroupName": resourceGroup,
|
||||
"profileName": name,
|
||||
}); err != nil {
|
||||
return nil, errors.Wrap(err, "expanding URL with parameters failed")
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "sending get network profile request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) is a success response.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode the body from the response.
|
||||
if resp.Body == nil {
|
||||
return nil, errors.New("get network profile returned an empty body in the response")
|
||||
}
|
||||
var p network.Profile
|
||||
if err := json.NewDecoder(resp.Body).Decode(&p); err != nil {
|
||||
return nil, errors.Wrap(err, "decoding get network profile response body failed")
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateProfile creates or updates an Azure network profile
|
||||
func (c *Client) CreateOrUpdateProfile(resourceGroup string, p *network.Profile) (*network.Profile, error) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(baseURI, profilePath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
b, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "marshalling networking profile failed")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", uri, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating network profile create uri request failed")
|
||||
}
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroupName": resourceGroup,
|
||||
"profileName": *p.Name,
|
||||
}); err != nil {
|
||||
return nil, errors.Wrap(err, "expanding URL with parameters failed")
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "sending get network profile request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) is a success response.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode the body from the response.
|
||||
if resp.Body == nil {
|
||||
return nil, errors.New("create network profile returned an empty body in the response")
|
||||
}
|
||||
|
||||
var profile network.Profile
|
||||
if err := json.NewDecoder(resp.Body).Decode(&profile); err != nil {
|
||||
return nil, errors.Wrap(err, "decoding create network profile response body failed")
|
||||
}
|
||||
|
||||
return &profile, nil
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-08-01/network"
|
||||
)
|
||||
|
||||
func TestGetProfileNotFound(t *testing.T) {
|
||||
c := newTestClient(t)
|
||||
p, err := c.GetProfile(resourceGroup, "someprofile")
|
||||
if err == nil {
|
||||
t.Fatalf("expect error when getting the non-exist profile: %v", p)
|
||||
}
|
||||
if !IsNotFound(err) {
|
||||
t.Fatal("expect NotFound error")
|
||||
}
|
||||
if p != nil {
|
||||
t.Fatal("unexpected profile")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateGetProfile(t *testing.T) {
|
||||
c := newTestClient(t)
|
||||
ensureVnet(t, t.Name())
|
||||
|
||||
subnet := NewSubnetWithContainerInstanceDelegation(t.Name(), "10.0.0.0/24")
|
||||
|
||||
subnet, err := c.CreateOrUpdateSubnet(resourceGroup, t.Name(), subnet)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p := NewNetworkProfile(t.Name(), location, *subnet.ID)
|
||||
|
||||
p1, err := c.CreateOrUpdateProfile(resourceGroup, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if p1 == nil {
|
||||
t.Fatal("create profile should return profile")
|
||||
}
|
||||
if p1.ID == nil || *p1.ID == "" {
|
||||
t.Fatal("create profile should return profile.ID")
|
||||
}
|
||||
|
||||
var p2 *network.Profile
|
||||
p2, err = c.GetProfile(resourceGroup, *p.Name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(*p2.ProfilePropertiesFormat.ContainerNetworkInterfaceConfigurations) != 1 {
|
||||
t.Fatalf("got unexpected profile properties: %+v", *p2.ProfilePropertiesFormat)
|
||||
}
|
||||
|
||||
containterNetworkInterfaceConfiguration := (*p2.ProfilePropertiesFormat.ContainerNetworkInterfaceConfigurations)[0]
|
||||
if len(*containterNetworkInterfaceConfiguration.ContainerNetworkInterfaceConfigurationPropertiesFormat.IPConfigurations) != 1 {
|
||||
t.Fatalf("got unexpected profile IP configuration: %+v", *containterNetworkInterfaceConfiguration.ContainerNetworkInterfaceConfigurationPropertiesFormat.IPConfigurations)
|
||||
}
|
||||
|
||||
ipConfiguration := (*containterNetworkInterfaceConfiguration.ContainerNetworkInterfaceConfigurationPropertiesFormat.IPConfigurations)[0]
|
||||
if *ipConfiguration.IPConfigurationProfilePropertiesFormat.Subnet.ID != *subnet.ID {
|
||||
t.Fatal("got unexpected subnet")
|
||||
}
|
||||
|
||||
subnet, err = c.GetSubnet(resourceGroup, t.Name(), t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(*subnet.SubnetPropertiesFormat.IPConfigurationProfiles) != 1 {
|
||||
t.Fatalf("got unexpected subnet IP configuration profiles: %+v", *subnet.SubnetPropertiesFormat.IPConfigurationProfiles)
|
||||
}
|
||||
|
||||
expected := path.Join(*p2.ID, "containerNetworkInterfaceConfigurations/eth0/ipConfigurations/ipconfigprofile1")
|
||||
if *(*subnet.SubnetPropertiesFormat.IPConfigurationProfiles)[0].ID != expected {
|
||||
t.Fatalf("got unexpected profile, expected:\n\t%s, got:\n\t%s", expected, *(*subnet.SubnetPropertiesFormat.IPConfigurationProfiles)[0].ID)
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-08-01/network"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
const (
|
||||
subnetPath = "subscriptions/{{.subscriptionId}}/resourcegroups/{{.resourceGroupName}}/providers/Microsoft.Network/virtualNetworks/{{.vnetName}}/subnets/{{.subnetName}}"
|
||||
subnetAction = "Microsoft.Network/virtualNetworks/subnets/action"
|
||||
)
|
||||
|
||||
var (
|
||||
delegationName = "aciDelegation"
|
||||
serviceName = "Microsoft.ContainerInstance/containerGroups"
|
||||
)
|
||||
|
||||
// NewSubnetWithContainerInstanceDelegation creates the subnet instance with ACI delegation
|
||||
func NewSubnetWithContainerInstanceDelegation(name, addressPrefix string) *network.Subnet {
|
||||
subnet := network.Subnet{
|
||||
Name: &name,
|
||||
SubnetPropertiesFormat: &network.SubnetPropertiesFormat{
|
||||
AddressPrefix: &addressPrefix,
|
||||
Delegations: &[]network.Delegation{
|
||||
network.Delegation{
|
||||
Name: &delegationName,
|
||||
ServiceDelegationPropertiesFormat: &network.ServiceDelegationPropertiesFormat{
|
||||
ServiceName: &serviceName,
|
||||
Actions: &[]string{subnetAction},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &subnet
|
||||
}
|
||||
|
||||
// GetSubnet gets the subnet from the specified resourcegroup/vnet
|
||||
func (c *Client) GetSubnet(resourceGroup, vnet, name string) (*network.Subnet, error) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(baseURI, subnetPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
req, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating subnet get uri request failed")
|
||||
}
|
||||
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroupName": resourceGroup,
|
||||
"subnetName": name,
|
||||
"vnetName": vnet,
|
||||
}); err != nil {
|
||||
return nil, errors.Wrap(err, "expanding URL with parameters failed")
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "sending subnet get request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) is a success response.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var subnet network.Subnet
|
||||
if err := json.NewDecoder(resp.Body).Decode(&subnet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &subnet, nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateSubnet creates a new or updates an existing subnet in the defined resourcegroup/vnet
|
||||
func (c *Client) CreateOrUpdateSubnet(resourceGroup, vnet string, s *network.Subnet) (*network.Subnet, error) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(baseURI, subnetPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "marshallig networking profile failed")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", uri, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, errors.New("creating subnet create uri request failed")
|
||||
}
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroupName": resourceGroup,
|
||||
"subnetName": *s.Name,
|
||||
"vnetName": vnet,
|
||||
}); err != nil {
|
||||
return nil, errors.Wrap(err, "expanding URL with parameters failed")
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "sending create subnet request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) is a success response.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var subnet network.Subnet
|
||||
if err := json.NewDecoder(resp.Body).Decode(&subnet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &subnet, nil
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-08-01/network"
|
||||
)
|
||||
|
||||
func TestCreateGetSubnet(t *testing.T) {
|
||||
c := newTestClient(t)
|
||||
|
||||
subnet := NewSubnetWithContainerInstanceDelegation(t.Name(), "10.0.0.0/24")
|
||||
|
||||
ensureVnet(t, t.Name())
|
||||
|
||||
s1, err := c.CreateOrUpdateSubnet(resourceGroup, t.Name(), subnet)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s1 == nil {
|
||||
t.Fatal("create subnet should return subnet")
|
||||
}
|
||||
if s1.ID == nil || *s1.ID == "" {
|
||||
t.Fatal("create subnet should return subnet.ID")
|
||||
}
|
||||
|
||||
var s2 *network.Subnet
|
||||
s2, err = c.GetSubnet(resourceGroup, t.Name(), *subnet.Name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if *s2.Name != *subnet.Name {
|
||||
t.Fatal("got unexpected subnet")
|
||||
}
|
||||
if *s2.SubnetPropertiesFormat.AddressPrefix != *subnet.SubnetPropertiesFormat.AddressPrefix {
|
||||
t.Fatalf("got unexpected address prefix: %s", *s2.SubnetPropertiesFormat.AddressPrefix)
|
||||
}
|
||||
if len(*s2.SubnetPropertiesFormat.Delegations) != 1 {
|
||||
t.Fatalf("got unexpected delgations: %v", *s2.SubnetPropertiesFormat.Delegations)
|
||||
}
|
||||
if *(*s2.SubnetPropertiesFormat.Delegations)[0].Name != *(*subnet.SubnetPropertiesFormat.Delegations)[0].Name {
|
||||
t.Fatalf("got unexpected delegation: %v", (*s2.SubnetPropertiesFormat.Delegations)[0])
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package resourcegroups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
azure "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
)
|
||||
|
||||
const (
|
||||
// BaseURI is the default URI used for compute services.
|
||||
BaseURI = "https://management.azure.com"
|
||||
defaultUserAgent = "virtual-kubelet/azure-arm-resourcegroups/2017-12-01"
|
||||
apiVersion = "2017-08-01"
|
||||
|
||||
resourceGroupURLPath = "subscriptions/{{.subscriptionId}}/resourcegroups/{{.resourceGroupName}}"
|
||||
)
|
||||
|
||||
// Client is a client for interacting with Azure resource groups.
|
||||
//
|
||||
// Clients should be reused instead of created as needed.
|
||||
// The methods of Client are safe for concurrent use by multiple goroutines.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
auth *azure.Authentication
|
||||
}
|
||||
|
||||
// NewClient creates a new Azure resource groups client.
|
||||
func NewClient(auth *azure.Authentication, extraUserAgent string) (*Client, error) {
|
||||
if auth == nil {
|
||||
return nil, fmt.Errorf("Authentication is not supplied for the Azure client")
|
||||
}
|
||||
|
||||
userAgent := []string{defaultUserAgent}
|
||||
if extraUserAgent != "" {
|
||||
userAgent = append(userAgent, extraUserAgent)
|
||||
}
|
||||
|
||||
client, err := azure.NewClient(auth, BaseURI, userAgent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating Azure client failed: %v", err)
|
||||
}
|
||||
|
||||
return &Client{hc: client.HTTPClient, auth: auth}, nil
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package resourcegroups
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
azure "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
)
|
||||
|
||||
var (
|
||||
client *Client
|
||||
location = "eastus"
|
||||
resourceGroup = "virtual-kubelet-tests"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Create a resource group name with uuid.
|
||||
uid := uuid.New()
|
||||
resourceGroup += "-" + uid.String()[0:6]
|
||||
}
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
auth, err := azure.NewAuthenticationFromFile("../../../../credentials.json")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load Azure authentication file: %v", err)
|
||||
}
|
||||
|
||||
c, err := NewClient(auth, "unit-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client = c
|
||||
}
|
||||
|
||||
func TestResourceGroupDoesNotExist(t *testing.T) {
|
||||
exists, err := client.ResourceGroupExists(resourceGroup)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if exists {
|
||||
t.Fatal("resource group should not exist before it has been created")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateResourceGroup(t *testing.T) {
|
||||
g, err := client.CreateResourceGroup(resourceGroup, Group{
|
||||
Location: location,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// check the name is the same
|
||||
if g.Name != resourceGroup {
|
||||
t.Fatalf("resource group name is %s, expected virtual-kubelet-tests", g.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceGroupExists(t *testing.T) {
|
||||
exists, err := client.ResourceGroupExists(resourceGroup)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !exists {
|
||||
t.Fatal("resource group should exist after being created")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetResourceGroup(t *testing.T) {
|
||||
g, err := client.GetResourceGroup(resourceGroup)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// check the name is the same
|
||||
if g.Name != resourceGroup {
|
||||
t.Fatalf("resource group name is %s, expected %s", g.Name, resourceGroup)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteResourceGroup(t *testing.T) {
|
||||
err := client.DeleteResourceGroup(resourceGroup)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package resourcegroups
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
// CreateResourceGroup creates a new Azure resource group with the
|
||||
// provided properties.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/resources/resourcegroups/createorupdate
|
||||
func (c *Client) CreateResourceGroup(resourceGroup string, properties Group) (*Group, error) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(BaseURI, resourceGroupURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the body for the request.
|
||||
b := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(b).Encode(properties); err != nil {
|
||||
return nil, fmt.Errorf("Encoding create resource group body request failed: %v", err)
|
||||
}
|
||||
|
||||
// Create the request.
|
||||
req, err := http.NewRequest("PUT", uri, b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating create/update resource group uri request failed: %v", err)
|
||||
}
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroupName": resourceGroup,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("Expanding URL with parameters failed: %v", err)
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Sending create resource group request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) and 201 (Created) are a successful responses.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode the body from the response.
|
||||
if resp.Body == nil {
|
||||
return nil, errors.New("Create resource group returned an empty body in the response")
|
||||
}
|
||||
var g Group
|
||||
if err := json.NewDecoder(resp.Body).Decode(&g); err != nil {
|
||||
return nil, fmt.Errorf("Decoding create resource group response body failed: %v", err)
|
||||
}
|
||||
|
||||
return &g, nil
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package resourcegroups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
// DeleteResourceGroup deletes an Azure resource group.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/resources/resourcegroups/delete
|
||||
func (c *Client) DeleteResourceGroup(resourceGroup string) error {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(BaseURI, resourceGroupURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
req, err := http.NewRequest("DELETE", uri, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Creating delete resource group uri request failed: %v", err)
|
||||
}
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroupName": resourceGroup,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("Expanding URL with parameters failed: %v", err)
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Sending delete resource group request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) and 202 (Accepted) are successful responses.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 204 No Content means the specified resource group was not found.
|
||||
if resp.StatusCode == http.StatusNoContent {
|
||||
return fmt.Errorf("Resource group with name %q was not found", resourceGroup)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// Package resourcegroups provides tools for interacting with the
|
||||
// Azure Resource Manager resource groups API.
|
||||
package resourcegroups
|
||||
@@ -1,60 +0,0 @@
|
||||
package resourcegroups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
// ResourceGroupExists checks if an Azure resource group exists.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/resources/resourcegroups/checkexistence
|
||||
func (c *Client) ResourceGroupExists(resourceGroup string) (bool, error) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(BaseURI, resourceGroupURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
req, err := http.NewRequest("HEAD", uri, nil)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Creating resource group exists uri request failed: %v", err)
|
||||
}
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroupName": resourceGroup,
|
||||
}); err != nil {
|
||||
return false, fmt.Errorf("Expanding URL with parameters failed: %v", err)
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Sending resource group exists request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// A 404 response means it does not exit.
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 204 (NoContent) and 404 are successful responses.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// A 204 status means it exists.
|
||||
if resp.StatusCode == http.StatusNoContent {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// A 404 status means it does not exist.
|
||||
return false, nil
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package resourcegroups
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
)
|
||||
|
||||
// GetResourceGroup gets an Azure resource group.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/resources/ResourceGroups/Get
|
||||
func (c *Client) GetResourceGroup(resourceGroup string) (*Group, error) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(BaseURI, resourceGroupURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
req, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating get resource group uri request failed: %v", err)
|
||||
}
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroupName": resourceGroup,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("Expanding URL with parameters failed: %v", err)
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Sending get resource group request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) is a success response.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode the body from the response.
|
||||
if resp.Body == nil {
|
||||
return nil, errors.New("Create resource group returned an empty body in the response")
|
||||
}
|
||||
var g Group
|
||||
if err := json.NewDecoder(resp.Body).Decode(&g); err != nil {
|
||||
return nil, fmt.Errorf("Decoding get resource group response body failed: %v", err)
|
||||
}
|
||||
|
||||
return &g, nil
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package resourcegroups
|
||||
|
||||
import "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
|
||||
// Group holds resource group information.
|
||||
type Group struct {
|
||||
api.ResponseMetadata `json:"-"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Properties *GroupProperties `json:"properties,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
ManagedBy string `json:"managedBy,omitempty"`
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// GroupProperties deines the properties for an Azure resource group.
|
||||
type GroupProperties struct {
|
||||
ProvisioningState string `json:"provisioningState,omitempty"`
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package resourcegroups
|
||||
|
||||
// UpdateResourceGroup updates an Azure resource group with the
|
||||
// provided properties.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/resources/resourcegroups/createorupdate
|
||||
func (c *Client) UpdateResourceGroup(resourceGroup string, properties Group) (*Group, error) {
|
||||
return c.CreateResourceGroup(resourceGroup, properties)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cpuguy83/strongerrors"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
"github.com/virtual-kubelet/azure-aci/client/api"
|
||||
)
|
||||
|
||||
func wrapError(err error) error {
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/cpuguy83/strongerrors/status/ocstatus"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/virtual-kubelet/azure-aci/client/aci"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/aci"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/trace"
|
||||
"golang.org/x/sync/errgroup"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/aci"
|
||||
"k8s.io/api/core/v1"
|
||||
"github.com/virtual-kubelet/azure-aci/client/aci"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
stats "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
@@ -42,7 +42,7 @@ func TestCollectMetrics(t *testing.T) {
|
||||
t.Fatalf("got unexpected container\nexpected:\n%+v\nactual:\n%+v", expectedContainer, actualContainer)
|
||||
}
|
||||
found = true
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user