Переглянути джерело

Merge pull request #300 from docker/aci_ci

Run ACI e2e tests in the CI with basic support for service principal login
Guillaume Tardif 5 роки тому
батько
коміт
f0ab42e109

+ 1 - 1
.github/workflows/ci.yml

@@ -54,4 +54,4 @@ jobs:
         run: make -f builder.Makefile cli
 
       - name: E2E Test
-        run: make e2e-local
+        run: make e2e-local

+ 7 - 0
.github/workflows/master-ci.yml

@@ -59,6 +59,13 @@ jobs:
       - name: E2E Test
         run: make e2e-local
 
+      - name: ACI e2e Test
+        env:
+          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
+          AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
+          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
+        run: make e2e-aci
+
   windows-build:
     name: Windows Build
     runs-on: windows-latest

+ 7 - 3
azure/convert/convert.go

@@ -21,6 +21,7 @@ import (
 	"errors"
 	"fmt"
 	"io/ioutil"
+	"math"
 	"strconv"
 	"strings"
 
@@ -267,7 +268,9 @@ func (s serviceConfigAciHelper) getAciContainer(volumesCache map[string]bool) (c
 	memLimit := 1. // Default 1 Gb
 	var cpuLimit float64 = 1
 	if s.Deploy != nil && s.Deploy.Resources.Limits != nil {
-		memLimit = float64(bytesToGb(s.Deploy.Resources.Limits.MemoryBytes))
+		if s.Deploy.Resources.Limits.MemoryBytes != 0 {
+			memLimit = bytesToGb(s.Deploy.Resources.Limits.MemoryBytes)
+		}
 		if s.Deploy.Resources.Limits.NanoCPUs != "" {
 			cpuLimit, err = strconv.ParseFloat(s.Deploy.Resources.Limits.NanoCPUs, 0)
 			if err != nil {
@@ -295,8 +298,9 @@ func (s serviceConfigAciHelper) getAciContainer(volumesCache map[string]bool) (c
 
 }
 
-func bytesToGb(b types.UnitBytes) int64 {
-	return int64(b) / 1024 / 1024 / 1024 // from bytes to gigabytes
+func bytesToGb(b types.UnitBytes) float64 {
+	f := float64(b) / 1024 / 1024 / 1024 // from bytes to gigabytes
+	return math.Round(f*100) / 100
 }
 
 // ContainerGroupToContainer composes a Container from an ACI container definition

+ 61 - 0
azure/convert/convert_test.go

@@ -213,6 +213,67 @@ func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerMultiplePorts
 	Expect(*groupPorts[1].Port).To(Equal(int32(8080)))
 }
 
+func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerResourceLimits() {
+	_0_1Gb := 0.1 * 1024 * 1024 * 1024
+	project := compose.Project{
+		Name: "",
+		Config: types.Config{
+			Services: []types.ServiceConfig{
+				{
+					Name:  "service1",
+					Image: "image1",
+					Deploy: &types.DeployConfig{
+						Resources: types.Resources{
+							Limits: &types.Resource{
+								NanoCPUs:    "0.1",
+								MemoryBytes: types.UnitBytes(_0_1Gb),
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	group, err := ToContainerGroup(suite.ctx, project)
+	Expect(err).To(BeNil())
+
+	container1 := (*group.Containers)[0]
+	limits := *container1.Resources.Limits
+	Expect(*limits.CPU).To(Equal(float64(0.1)))
+	Expect(*limits.MemoryInGB).To(Equal(float64(0.1)))
+}
+
+func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerResourceLimitsDefaults() {
+	project := compose.Project{
+		Name: "",
+		Config: types.Config{
+			Services: []types.ServiceConfig{
+				{
+					Name:  "service1",
+					Image: "image1",
+					Deploy: &types.DeployConfig{
+						Resources: types.Resources{
+							Limits: &types.Resource{
+								NanoCPUs:    "",
+								MemoryBytes: 0,
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	group, err := ToContainerGroup(suite.ctx, project)
+	Expect(err).To(BeNil())
+
+	container1 := (*group.Containers)[0]
+	limits := *container1.Resources.Limits
+	Expect(*limits.CPU).To(Equal(float64(1)))
+	Expect(*limits.MemoryInGB).To(Equal(float64(1)))
+}
+
 func TestConvertTestSuite(t *testing.T) {
 	RegisterTestingT(t)
 	suite.Run(t, new(ConvertTestSuite))

+ 42 - 0
azure/login/login.go

@@ -31,6 +31,7 @@ import (
 
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/adal"
+	auth2 "github.com/Azure/go-autorest/autorest/azure/auth"
 	"github.com/Azure/go-autorest/autorest/azure/cli"
 	"github.com/Azure/go-autorest/autorest/date"
 	"github.com/pkg/errors"
@@ -93,6 +94,32 @@ func newAzureLoginServiceFromPath(tokenStorePath string, helper apiHelper) (Azur
 	}, nil
 }
 
+// TestLoginFromServicePrincipal login with clientId / clientSecret from a previously created service principal.
+// The resulting token does not include a refresh token, used for tests only
+func (login AzureLoginService) TestLoginFromServicePrincipal(clientID string, clientSecret string, tenantID string) error {
+	// Tried with auth2.NewUsernamePasswordConfig() but could not make this work with username / password, setting this for CI with clientID / clientSecret
+	creds := auth2.NewClientCredentialsConfig(clientID, clientSecret, tenantID)
+
+	spToken, err := creds.ServicePrincipalToken()
+	if err != nil {
+		return errors.Wrapf(errdefs.ErrLoginFailed, "could not  login with service principal: %s", err)
+	}
+	err = spToken.Refresh()
+	if err != nil {
+		return errors.Wrapf(errdefs.ErrLoginFailed, "could not  login with service principal: %s", err)
+	}
+	token, err := spToOAuthToken(spToken.Token())
+	if err != nil {
+		return errors.Wrapf(errdefs.ErrLoginFailed, "could not read service principal token expiry: %s", err)
+	}
+	loginInfo := TokenInfo{TenantID: tenantID, Token: token}
+
+	if err := login.tokenStore.writeLoginInfo(loginInfo); err != nil {
+		return errors.Wrapf(errdefs.ErrLoginFailed, "could not store login info: %s", err)
+	}
+	return nil
+}
+
 // Login performs an Azure login through a web browser
 func (login AzureLoginService) Login(ctx context.Context) error {
 	queryCh := make(chan localResponse, 1)
@@ -179,6 +206,21 @@ func toOAuthToken(token azureToken) oauth2.Token {
 	return oauthToken
 }
 
+func spToOAuthToken(token adal.Token) (oauth2.Token, error) {
+	expiresIn, err := token.ExpiresIn.Int64()
+	if err != nil {
+		return oauth2.Token{}, err
+	}
+	expireTime := time.Now().Add(time.Duration(expiresIn) * time.Second)
+	oauthToken := oauth2.Token{
+		RefreshToken: token.RefreshToken,
+		AccessToken:  token.AccessToken,
+		Expiry:       expireTime,
+		TokenType:    token.Type,
+	}
+	return oauthToken, nil
+}
+
 // NewAuthorizerFromLogin creates an authorizer based on login access token
 func NewAuthorizerFromLogin() (autorest.Authorizer, error) {
 	return newAuthorizerFromLoginStorePath(getTokenStorePath())

+ 2 - 1
cli/mobycli/exec_test.go

@@ -3,9 +3,10 @@ package mobycli
 import (
 	"testing"
 
-	"github.com/docker/api/tests/framework"
 	. "github.com/onsi/gomega"
 	"github.com/stretchr/testify/suite"
+
+	"github.com/docker/api/tests/framework"
 )
 
 type MobyExecSuite struct {

+ 1 - 0
go.mod

@@ -8,6 +8,7 @@ require (
 	github.com/Azure/azure-storage-file-go v0.7.0
 	github.com/Azure/go-autorest/autorest v0.11.0
 	github.com/Azure/go-autorest/autorest/adal v0.9.0
+	github.com/Azure/go-autorest/autorest/azure/auth v0.5.0
 	github.com/Azure/go-autorest/autorest/azure/cli v0.4.0
 	github.com/Azure/go-autorest/autorest/date v0.3.0
 	github.com/Azure/go-autorest/autorest/to v0.4.0

+ 2 - 0
go.sum

@@ -18,6 +18,8 @@ github.com/Azure/go-autorest/autorest v0.11.0/go.mod h1:JFgpikqFJ/MleTTxwepExTKn
 github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
 github.com/Azure/go-autorest/autorest/adal v0.9.0 h1:SigMbuFNuKgc1xcGhaeapbh+8fgsu+GxgDRFyg7f5lM=
 github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.0 h1:nSMjYIe24eBYasAIxt859TxyXef/IqoH+8/g4+LmcVs=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.0/go.mod h1:QRTvSZQpxqm8mSErhnbI+tANIBAKP7B+UIE2z4ypUO0=
 github.com/Azure/go-autorest/autorest/azure/cli v0.4.0 h1:Ml+UCrnlKD+cJmSzrZ/RDcDw86NjkRUpnFh7V5JUhzU=
 github.com/Azure/go-autorest/autorest/azure/cli v0.4.0/go.mod h1:JljT387FplPzBA31vUcvsetLKF3pec5bdAxjVU4kI2s=
 github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=

+ 13 - 0
tests/aci-e2e/e2e-aci_test.go

@@ -20,10 +20,12 @@ import (
 	"context"
 	"fmt"
 	"net/url"
+	"os"
 	"strings"
 	"testing"
 
 	"github.com/docker/api/azure"
+	"github.com/docker/api/azure/login"
 
 	"github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/resources"
 	azure_storage "github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/storage/mgmt/storage"
@@ -69,6 +71,17 @@ func (s *E2eACISuite) TestContextDefault() {
 }
 
 func (s *E2eACISuite) TestACIBackend() {
+	It("Logs in azure using service principal credentials", func() {
+		login, err := login.NewAzureLoginService()
+		Expect(err).To(BeNil())
+		// in order to create new service principal and get these 3 values : `az ad sp create-for-rbac --name 'TestServicePrincipal' --sdk-auth`
+		clientID := os.Getenv("AZURE_CLIENT_ID")
+		clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
+		tenantID := os.Getenv("AZURE_TENANT_ID")
+		err = login.TestLoginFromServicePrincipal(clientID, clientSecret, tenantID)
+		Expect(err).To(BeNil())
+	})
+
 	It("creates a new aci context for tests", func() {
 		setupTestResourceGroup(resourceGroupName)
 		helper := azure.NewACIResourceGroupHelper()