/* Copyright 2020 Docker, Inc. 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. */ package main 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" "github.com/Azure/azure-storage-file-go/azfile" "github.com/Azure/go-autorest/autorest/to" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" "github.com/docker/api/context/store" "github.com/docker/api/tests/aci-e2e/storage" . "github.com/docker/api/tests/framework" ) const ( resourceGroupName = "resourceGroupTest" location = "westeurope" contextName = "acitest" testContainerName = "testcontainername" ) var ( subscriptionID string ) type E2eACISuite struct { Suite } func (s *E2eACISuite) TestContextDefault() { It("should be initialized with default context", func() { _, err := s.NewCommand("docker", "context", "rm", "-f", contextName).Exec() if err == nil { log.Println("Cleaning existing test context") } s.NewCommand("docker", "context", "use", "default").ExecOrDie() output := s.NewCommand("docker", "context", "ls").ExecOrDie() Expect(output).To(Not(ContainSubstring(contextName))) Expect(output).To(ContainSubstring("default *")) }) } 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() models, err := helper.GetSubscriptionIDs(context.TODO()) Expect(err).To(BeNil()) subscriptionID = *models[0].SubscriptionID s.NewDockerCommand("context", "create", "aci", contextName, "--subscription-id", subscriptionID, "--resource-group", resourceGroupName, "--location", location).ExecOrDie() // Expect(output).To(ContainSubstring("ACI context acitest created")) }) defer deleteResourceGroup(resourceGroupName) It("uses the aci context", func() { currentContext := s.NewCommand("docker", "context", "use", contextName).ExecOrDie() Expect(currentContext).To(ContainSubstring(contextName)) output := s.NewCommand("docker", "context", "ls").ExecOrDie() Expect(output).To(ContainSubstring("acitest *")) }) It("ensures no container is running initially", func() { output := s.NewDockerCommand("ps").ExecOrDie() Expect(len(Lines(output))).To(Equal(1)) }) It("runs nginx on port 80", func() { aciContext := store.AciContext{ SubscriptionID: subscriptionID, Location: location, ResourceGroup: resourceGroupName, } createStorageAccount(aciContext, testStorageAccountName) defer deleteStorageAccount(aciContext) keys := getStorageKeys(aciContext, testStorageAccountName) firstKey := *keys[0].Value credential, u := createFileShare(firstKey, testShareName) uploadFile(credential, u.String(), testFileName, testFileContent) mountTarget := "/usr/share/nginx/html" output := s.NewDockerCommand("run", "nginx", "-v", fmt.Sprintf("%s:%s@%s:%s", testStorageAccountName, firstKey, testShareName, mountTarget), "-p", "80:80", "--name", testContainerName).ExecOrDie() Expect(output).To(ContainSubstring(testContainerName)) output = s.NewDockerCommand("ps").ExecOrDie() lines := Lines(output) Expect(len(lines)).To(Equal(2)) containerFields := Columns(lines[1]) Expect(containerFields[1]).To(Equal("nginx")) Expect(containerFields[2]).To(Equal("Running")) exposedIP := containerFields[3] containerID := containerFields[0] Expect(exposedIP).To(ContainSubstring(":80->80/tcp")) publishedURL := strings.ReplaceAll(exposedIP, "->80/tcp", "") output = s.NewCommand("curl", publishedURL).ExecOrDie() Expect(output).To(ContainSubstring(testFileContent)) output = s.NewDockerCommand("logs", containerID).ExecOrDie() Expect(output).To(ContainSubstring("GET")) }) It("removes container nginx", func() { output := s.NewDockerCommand("rm", testContainerName).ExecOrDie() Expect(Lines(output)[0]).To(Equal(testContainerName)) }) var exposedURL string const composeFile = "../composefiles/aci-demo/aci_demo_port.yaml" const composeFileMultiplePorts = "../composefiles/aci-demo/aci_demo_multi_port.yaml" const serverContainer = "acidemo_web" const wordsContainer = "acidemo_words" It("deploys a compose app", func() { s.NewDockerCommand("compose", "up", "-f", composeFile, "--project-name", "acidemo").ExecOrDie() // Expect(output).To(ContainSubstring("Successfully deployed")) output := s.NewDockerCommand("ps").ExecOrDie() Lines := Lines(output) Expect(len(Lines)).To(Equal(4)) webChecked := false for _, line := range Lines[1:] { Expect(line).To(ContainSubstring("Running")) if strings.Contains(line, serverContainer) { webChecked = true containerFields := Columns(line) exposedIP := containerFields[3] Expect(exposedIP).To(ContainSubstring(":80->80/tcp")) exposedURL = strings.ReplaceAll(exposedIP, "->80/tcp", "") output = s.NewCommand("curl", exposedURL).ExecOrDie() Expect(output).To(ContainSubstring("Docker Compose demo")) output = s.NewCommand("curl", exposedURL+"/words/noun").ExecOrDie() Expect(output).To(ContainSubstring("\"word\":")) } } Expect(webChecked).To(BeTrue()) }) It("get logs from web service", func() { output := s.NewDockerCommand("logs", serverContainer).ExecOrDie() Expect(output).To(ContainSubstring("Listening on port 80")) }) It("updates a compose app", func() { s.NewDockerCommand("compose", "up", "-f", composeFileMultiplePorts, "--project-name", "acidemo").ExecOrDie() // Expect(output).To(ContainSubstring("Successfully deployed")) output := s.NewDockerCommand("ps").ExecOrDie() Lines := Lines(output) Expect(len(Lines)).To(Equal(4)) webChecked := false wordsChecked := false for _, line := range Lines[1:] { Expect(line).To(ContainSubstring("Running")) if strings.Contains(line, serverContainer) { webChecked = true containerFields := Columns(line) exposedIP := containerFields[3] Expect(exposedIP).To(ContainSubstring(":80->80/tcp")) url := strings.ReplaceAll(exposedIP, "->80/tcp", "") Expect(exposedURL).To(Equal(url)) } if strings.Contains(line, wordsContainer) { wordsChecked = true containerFields := Columns(line) exposedIP := containerFields[3] Expect(exposedIP).To(ContainSubstring(":8080->8080/tcp")) url := strings.ReplaceAll(exposedIP, "->8080/tcp", "") output = s.NewCommand("curl", url+"/noun").ExecOrDie() Expect(output).To(ContainSubstring("\"word\":")) } } Expect(webChecked).To(BeTrue()) Expect(wordsChecked).To(BeTrue()) }) It("shutdown compose app", func() { s.NewDockerCommand("compose", "down", "-f", composeFile, "--project-name", "acidemo").ExecOrDie() }) It("switches back to default context", func() { output := s.NewCommand("docker", "context", "use", "default").ExecOrDie() Expect(output).To(ContainSubstring("default")) }) It("deletes test context", func() { output := s.NewCommand("docker", "context", "rm", contextName).ExecOrDie() Expect(output).To(ContainSubstring(contextName)) }) } const ( testStorageAccountName = "dockertestaccount" testShareName = "dockertestshare" testFileContent = "Volume mounted with success!" testFileName = "index.html" ) func createStorageAccount(aciContext store.AciContext, accountName string) azure_storage.Account { log.Println("Creating storage account " + accountName) storageAccount, err := storage.CreateStorageAccount(context.TODO(), aciContext, accountName) Expect(err).To(BeNil()) Expect(*storageAccount.Name).To(Equal(accountName)) return storageAccount } func getStorageKeys(aciContext store.AciContext, storageAccountName string) []azure_storage.AccountKey { list, err := storage.ListKeys(context.TODO(), aciContext, storageAccountName) Expect(err).To(BeNil()) Expect(list.Keys).ToNot(BeNil()) Expect(len(*list.Keys)).To(BeNumerically(">", 0)) return *list.Keys } func deleteStorageAccount(aciContext store.AciContext) { log.Println("Deleting storage account " + testStorageAccountName) _, err := storage.DeleteStorageAccount(context.TODO(), aciContext, testStorageAccountName) Expect(err).To(BeNil()) } func createFileShare(key, shareName string) (azfile.SharedKeyCredential, url.URL) { // Create a ShareURL object that wraps a soon-to-be-created share's URL and a default pipeline. u, _ := url.Parse(fmt.Sprintf("https://%s.file.core.windows.net/%s", testStorageAccountName, shareName)) credential, err := azfile.NewSharedKeyCredential(testStorageAccountName, key) Expect(err).To(BeNil()) shareURL := azfile.NewShareURL(*u, azfile.NewPipeline(credential, azfile.PipelineOptions{})) _, err = shareURL.Create(context.TODO(), azfile.Metadata{}, 0) Expect(err).To(BeNil()) return *credential, *u } func uploadFile(credential azfile.SharedKeyCredential, baseURL, fileName, fileContent string) { fURL, err := url.Parse(baseURL + "/" + fileName) Expect(err).To(BeNil()) fileURL := azfile.NewFileURL(*fURL, azfile.NewPipeline(&credential, azfile.PipelineOptions{})) err = azfile.UploadBufferToAzureFile(context.TODO(), []byte(fileContent), fileURL, azfile.UploadToAzureFileOptions{}) Expect(err).To(BeNil()) } func TestE2eACI(t *testing.T) { suite.Run(t, new(E2eACISuite)) } func setupTestResourceGroup(groupName string) { log.Println("Creating resource group " + resourceGroupName) ctx := context.TODO() helper := azure.NewACIResourceGroupHelper() models, err := helper.GetSubscriptionIDs(ctx) Expect(err).To(BeNil()) _, err = helper.CreateOrUpdate(ctx, *models[0].SubscriptionID, groupName, resources.Group{ Location: to.StringPtr(location), }) Expect(err).To(BeNil()) } func deleteResourceGroup(groupName string) { log.Println("Deleting resource group " + resourceGroupName) ctx := context.TODO() helper := azure.NewACIResourceGroupHelper() models, err := helper.GetSubscriptionIDs(ctx) Expect(err).To(BeNil()) err = helper.Delete(ctx, *models[0].SubscriptionID, groupName) Expect(err).To(BeNil()) }