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

201
vendor/github.com/virtual-kubelet/azure-aci/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,39 @@
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

@@ -0,0 +1,60 @@
package aci
import (
"fmt"
"net/http"
"go.opencensus.io/plugin/ochttp/propagation/b3"
"go.opencensus.io/plugin/ochttp"
azure "github.com/virtual-kubelet/azure-aci/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

@@ -0,0 +1,71 @@
package aci
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"github.com/virtual-kubelet/azure-aci/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

@@ -0,0 +1,52 @@
package aci
import (
"context"
"fmt"
"net/http"
"net/url"
"github.com/virtual-kubelet/azure-aci/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

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

View File

@@ -0,0 +1,83 @@
package aci
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"github.com/virtual-kubelet/azure-aci/client/api"
)
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 TerminalSizeRequest) (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 = terminalSize.Height
xc.TerminalSize.Cols = 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

@@ -0,0 +1,64 @@
package aci
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"github.com/virtual-kubelet/azure-aci/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

@@ -0,0 +1,71 @@
package aci
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"github.com/virtual-kubelet/azure-aci/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

@@ -0,0 +1,66 @@
package aci
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"github.com/virtual-kubelet/azure-aci/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

@@ -0,0 +1,97 @@
package aci
import (
"context"
"encoding/json"
"net/http"
"net/url"
"path"
"time"
"github.com/pkg/errors"
"github.com/virtual-kubelet/azure-aci/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

@@ -0,0 +1,490 @@
package aci
import (
"time"
"github.com/virtual-kubelet/azure-aci/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

@@ -0,0 +1,10 @@
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

@@ -0,0 +1,69 @@
// 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

@@ -0,0 +1,43 @@
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

@@ -0,0 +1,51 @@
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

@@ -0,0 +1,104 @@
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

@@ -0,0 +1,128 @@
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

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

View File

@@ -0,0 +1,114 @@
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

@@ -0,0 +1,68 @@
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/azure-aci/client"
"github.com/virtual-kubelet/azure-aci/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

@@ -0,0 +1,151 @@
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/azure-aci/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

@@ -0,0 +1,136 @@
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/azure-aci/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
}