Move aci client (#531)

* Add azure-aci client dep

* Use aci client from new repo
This commit is contained in:
Brian Goff
2019-02-25 17:15:25 -08:00
committed by GitHub
parent 1bfffa975e
commit d19e8e5e27
48 changed files with 268 additions and 1495 deletions

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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"

View File

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

View File

@@ -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
}

View File

@@ -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")
}
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -1,2 +0,0 @@
// Package aci provides tools for interacting with the Azure Container Instances API.
package aci

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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"
)

View File

@@ -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)
}

View File

@@ -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,
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -1,3 +0,0 @@
// Package azure and subpackages are used to perform operations using the
// Azure Resource Manager (ARM).
package azure

View File

@@ -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",
}
)

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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])
}
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -1,3 +0,0 @@
// Package resourcegroups provides tools for interacting with the
// Azure Resource Manager resource groups API.
package resourcegroups

View File

@@ -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
}

View File

@@ -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
}

View File

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

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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"

View File

@@ -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
}
}