mirror of https://github.com/docker/compose.git
Merge pull request #300 from docker/aci_ci
Run ACI e2e tests in the CI with basic support for service principal login
This commit is contained in:
commit
f0ab42e109
|
@ -59,6 +59,13 @@ jobs:
|
||||||
- name: E2E Test
|
- name: E2E Test
|
||||||
run: make e2e-local
|
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:
|
windows-build:
|
||||||
name: Windows Build
|
name: Windows Build
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -267,7 +268,9 @@ func (s serviceConfigAciHelper) getAciContainer(volumesCache map[string]bool) (c
|
||||||
memLimit := 1. // Default 1 Gb
|
memLimit := 1. // Default 1 Gb
|
||||||
var cpuLimit float64 = 1
|
var cpuLimit float64 = 1
|
||||||
if s.Deploy != nil && s.Deploy.Resources.Limits != nil {
|
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 != "" {
|
if s.Deploy.Resources.Limits.NanoCPUs != "" {
|
||||||
cpuLimit, err = strconv.ParseFloat(s.Deploy.Resources.Limits.NanoCPUs, 0)
|
cpuLimit, err = strconv.ParseFloat(s.Deploy.Resources.Limits.NanoCPUs, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -295,8 +298,9 @@ func (s serviceConfigAciHelper) getAciContainer(volumesCache map[string]bool) (c
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func bytesToGb(b types.UnitBytes) int64 {
|
func bytesToGb(b types.UnitBytes) float64 {
|
||||||
return int64(b) / 1024 / 1024 / 1024 // from bytes to gigabytes
|
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
|
// ContainerGroupToContainer composes a Container from an ACI container definition
|
||||||
|
|
|
@ -213,6 +213,67 @@ func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerMultiplePorts
|
||||||
Expect(*groupPorts[1].Port).To(Equal(int32(8080)))
|
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) {
|
func TestConvertTestSuite(t *testing.T) {
|
||||||
RegisterTestingT(t)
|
RegisterTestingT(t)
|
||||||
suite.Run(t, new(ConvertTestSuite))
|
suite.Run(t, new(ConvertTestSuite))
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
|
|
||||||
"github.com/Azure/go-autorest/autorest"
|
"github.com/Azure/go-autorest/autorest"
|
||||||
"github.com/Azure/go-autorest/autorest/adal"
|
"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/azure/cli"
|
||||||
"github.com/Azure/go-autorest/autorest/date"
|
"github.com/Azure/go-autorest/autorest/date"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -93,6 +94,32 @@ func newAzureLoginServiceFromPath(tokenStorePath string, helper apiHelper) (Azur
|
||||||
}, nil
|
}, 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
|
// Login performs an Azure login through a web browser
|
||||||
func (login AzureLoginService) Login(ctx context.Context) error {
|
func (login AzureLoginService) Login(ctx context.Context) error {
|
||||||
queryCh := make(chan localResponse, 1)
|
queryCh := make(chan localResponse, 1)
|
||||||
|
@ -179,6 +206,21 @@ func toOAuthToken(token azureToken) oauth2.Token {
|
||||||
return oauthToken
|
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
|
// NewAuthorizerFromLogin creates an authorizer based on login access token
|
||||||
func NewAuthorizerFromLogin() (autorest.Authorizer, error) {
|
func NewAuthorizerFromLogin() (autorest.Authorizer, error) {
|
||||||
return newAuthorizerFromLoginStorePath(getTokenStorePath())
|
return newAuthorizerFromLoginStorePath(getTokenStorePath())
|
||||||
|
|
|
@ -3,9 +3,10 @@ package mobycli
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/api/tests/framework"
|
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/docker/api/tests/framework"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MobyExecSuite struct {
|
type MobyExecSuite struct {
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -8,6 +8,7 @@ require (
|
||||||
github.com/Azure/azure-storage-file-go v0.7.0
|
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 v0.11.0
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.9.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/azure/cli v0.4.0
|
||||||
github.com/Azure/go-autorest/autorest/date v0.3.0
|
github.com/Azure/go-autorest/autorest/date v0.3.0
|
||||||
github.com/Azure/go-autorest/autorest/to v0.4.0
|
github.com/Azure/go-autorest/autorest/to v0.4.0
|
||||||
|
|
2
go.sum
2
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.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 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/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 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/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=
|
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||||
|
|
|
@ -20,10 +20,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/api/azure"
|
"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"
|
"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"
|
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() {
|
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() {
|
It("creates a new aci context for tests", func() {
|
||||||
setupTestResourceGroup(resourceGroupName)
|
setupTestResourceGroup(resourceGroupName)
|
||||||
helper := azure.NewACIResourceGroupHelper()
|
helper := azure.NewACIResourceGroupHelper()
|
||||||
|
|
Loading…
Reference in New Issue