diff --git a/.gitignore b/.gitignore index e7c7bbd9..890297c8 100644 --- a/.gitignore +++ b/.gitignore @@ -65,4 +65,5 @@ source/OctopusTools.v2.ncrunchsolution /publish/ TestResult.xml TestResults/ -.idea/ \ No newline at end of file +.idea/ +*.runsettings \ No newline at end of file diff --git a/.teamcity/octopus/main/Extensions.kt b/.teamcity/octopus/main/Extensions.kt index e44d21ab..0d40fa5c 100644 --- a/.teamcity/octopus/main/Extensions.kt +++ b/.teamcity/octopus/main/Extensions.kt @@ -2,10 +2,12 @@ package octopus.main import jetbrains.buildServer.configs.kotlin.v2019_2.* import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.commitStatusPublisher +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.pullRequests +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.PullRequests fun BuildType.includeVcs(): BuildType { this.vcs { - root(AbsoluteId("SharedGitHubVcsRoot")) + root(AbsoluteId("OctopusDeploy_LIbraries_Sashimi_SharedGitHubVcsRoot")) excludeDefaultBranchChanges = true showDependenciesChanges = true @@ -19,7 +21,7 @@ fun BuildType.commitStatusPublisher(): BuildType { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } @@ -27,3 +29,17 @@ fun BuildType.commitStatusPublisher(): BuildType { return this } +fun BuildType.githubPullRequests(): BuildType { + this.features { + pullRequests { + provider = github { + authType = token { + token = "credentialsJSON:e3abf97f-cad5-4d88-9a7a-f588c55c53ed" + } + filterAuthorRole = PullRequests.GitHubRoleFilter.MEMBER + } + } + } + return this +} + diff --git a/.teamcity/octopus/main/MainProject.kt b/.teamcity/octopus/main/MainProject.kt index e7d974ed..04717bbf 100644 --- a/.teamcity/octopus/main/MainProject.kt +++ b/.teamcity/octopus/main/MainProject.kt @@ -15,7 +15,7 @@ object MainProject : Project({ PublishToFeedzIo ) - buildTypesToRegister.forEach { buildType(it.includeVcs().commitStatusPublisher()) } + buildTypesToRegister.forEach { buildType(it.includeVcs().commitStatusPublisher().githubPullRequests()) } buildTypesOrder = buildTypesToRegister.toList() sequential { diff --git a/.teamcity/octopus/main/MonoTestingProject.kt b/.teamcity/octopus/main/MonoTestingProject.kt index 5b6d5eca..cc1ede8c 100644 --- a/.teamcity/octopus/main/MonoTestingProject.kt +++ b/.teamcity/octopus/main/MonoTestingProject.kt @@ -50,7 +50,7 @@ class MonoTestingProject : Project({ }) } - buildTypesToRegister.forEach { buildType(it.commitStatusPublisher()) } + buildTypesToRegister.forEach { buildType(it.commitStatusPublisher().githubPullRequests()) } buildTypesOrder = buildTypes.toList() params { diff --git a/.teamcity/octopus/main/NetFxTestingProject.kt b/.teamcity/octopus/main/NetFxTestingProject.kt index 2aec3ecd..be869d7a 100644 --- a/.teamcity/octopus/main/NetFxTestingProject.kt +++ b/.teamcity/octopus/main/NetFxTestingProject.kt @@ -12,8 +12,6 @@ class NetFxTestingProject : Project({ val buildTypesToRegister = sequence { val items = listOf( - ("Windows 2008" to "2008"), - ("Windows 2008 R2" to "2008R2"), ("Windows 2012" to "2012"), ("Windows 2012 R2" to "2012R2"), ("Windows 2016" to "2016"), @@ -30,15 +28,11 @@ class NetFxTestingProject : Project({ equals("system.Octopus.OSVersion", item.second) } } - if(item.second.startsWith("2008")) { - yield(CalamariOnlyTestBuildType(block)) - } else { - yield(DotNetTestBuildType(block)) - } + yield(DotNetTestBuildType(block)) } } - buildTypesToRegister.forEach { buildType(it.commitStatusPublisher()) } + buildTypesToRegister.forEach { buildType(it.commitStatusPublisher().githubPullRequests()) } buildTypesOrder = buildTypes.toList() params { diff --git a/.teamcity/octopus/main/NetcoreTestingProject.kt b/.teamcity/octopus/main/NetcoreTestingProject.kt index 4343ab57..4955c5a1 100644 --- a/.teamcity/octopus/main/NetcoreTestingProject.kt +++ b/.teamcity/octopus/main/NetcoreTestingProject.kt @@ -13,7 +13,6 @@ class NetcoreTestingProject : Project({ ("Amazon Linux" to "AmazonLinux"), ("Ubuntu" to "Ubuntu"), ("openSUSE Leap" to "openSUSE"), - ("SUSE LES" to "SLES"), ("CentOS" to "CentOS"), ("Fedora" to "Fedora"), ("Debian" to "Debian"), @@ -48,20 +47,9 @@ class NetcoreTestingProject : Project({ exists("system.Octopus.DotnetSdk3.1") } }) - yield(DotNetTestBuildType { - id("Mac OSX".toId(projectName.toId())) - name = "Mac OSX" - params { - param("dotnet_runtime", "osx-x64") - } - requirements { - exists("DotNetCLI") - equals("teamcity.agent.jvm.os.name", "Mac OS X") - } - }) } - buildTypesToRegister.forEach { buildType(it.commitStatusPublisher()) } + buildTypesToRegister.forEach { buildType(it.commitStatusPublisher().githubPullRequests()) } buildTypesOrder = buildTypes.toList() params { diff --git a/.teamcity/octopus/main/buildtypes/PublishToFeedzIo.kt b/.teamcity/octopus/main/buildtypes/PublishToFeedzIo.kt index 4fa70b89..59695c17 100644 --- a/.teamcity/octopus/main/buildtypes/PublishToFeedzIo.kt +++ b/.teamcity/octopus/main/buildtypes/PublishToFeedzIo.kt @@ -1,11 +1,12 @@ package octopus.main.buildtypes +import jetbrains.buildServer.configs.kotlin.v2019_2.* import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.nuGetPublish import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.vcs object PublishToFeedzIo : BuildType({ - name = "Publish to Feedz.io" + name = "Chain: Build and Test and Publish to Feedz.io" buildNumberPattern = "${Build.depParamRefs.buildNumber}" @@ -15,7 +16,7 @@ object PublishToFeedzIo : BuildType({ toolPath = "%teamcity.tool.NuGet.CommandLine.DEFAULT%" packages = "*.nupkg" serverUrl = "%InternalNuget.OctopusDependeciesFeedUrl%" - apiKey = "%nuGetPublish.apiKey%" + apiKey = "credentialsJSON:a7d4426a-7256-4df7-a953-266292e6ad81" args = "-Timeout 1200" } } @@ -23,24 +24,101 @@ object PublishToFeedzIo : BuildType({ triggers { vcs { branchFilter = """ - ## We actually want to publish all builds - +:refs/tags/* +: - +:refs/heads/* + +:pull/* + +:refs/tags/* """.trimIndent() } } dependencies { dependency(Build) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + } + artifacts { cleanDestination = true artifactRules = "*.nupkg" } } + + dependency(RelativeId("NetcoreTesting_AmazonLinux")){ + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } + dependency(RelativeId("NetcoreTesting_Ubuntu")){ + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } + dependency(RelativeId("NetcoreTesting_OpenSUSE")){ + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } + dependency(RelativeId("NetcoreTesting_CentOS")){ + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } + dependency(RelativeId("NetcoreTesting_Fedora")){ + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } + dependency(RelativeId("NetcoreTesting_Debian")){ + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } + dependency(RelativeId("NetcoreTesting_Rhel")){ + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } + dependency(RelativeId("NetcoreTesting_Windows")){ + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } + + dependency(RelativeId("WindowsNetFxTesting_2012")){ + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } + dependency(RelativeId("WindowsNetFxTesting_2012r2")){ + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } + dependency(RelativeId("WindowsNetFxTesting_2016")){ + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } + dependency(RelativeId("WindowsNetFxTesting_2019")){ + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } } requirements { - equals("system.Octopus.Purpose", "Build") + startsWith("system.agent.name", "nautilus-") } }) \ No newline at end of file diff --git a/.teamcity/octopus/main/buildtypes/TestBuildType.kt b/.teamcity/octopus/main/buildtypes/TestBuildType.kt index 4c749026..267921d4 100644 --- a/.teamcity/octopus/main/buildtypes/TestBuildType.kt +++ b/.teamcity/octopus/main/buildtypes/TestBuildType.kt @@ -15,7 +15,7 @@ abstract class TestBuildType(block: BuildType.() -> Unit) : BuildType({ buildNumberPattern = "${Build.depParamRefs.buildNumber}" vcs { - AbsoluteId("SharedGitHubVcsRoot") + AbsoluteId("OctopusDeploy_LIbraries_Sashimi_SharedGitHubVcsRoot") checkoutMode = CheckoutMode.MANUAL cleanCheckout = true diff --git a/.teamcity/patches/buildTypes/Build.kts b/.teamcity/patches/buildTypes/Build.kts index cca18f16..b2ce5b87 100644 --- a/.teamcity/patches/buildTypes/Build.kts +++ b/.teamcity/patches/buildTypes/Build.kts @@ -45,10 +45,18 @@ changeBuildType(RelativeId("Build")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/.teamcity/patches/buildTypes/NetcoreTesting_AmazonLinux.kts b/.teamcity/patches/buildTypes/NetcoreTesting_AmazonLinux.kts index cebf1bed..b402c2d3 100644 --- a/.teamcity/patches/buildTypes/NetcoreTesting_AmazonLinux.kts +++ b/.teamcity/patches/buildTypes/NetcoreTesting_AmazonLinux.kts @@ -21,10 +21,18 @@ changeBuildType(RelativeId("NetcoreTesting_AmazonLinux")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/.teamcity/patches/buildTypes/NetcoreTesting_CentOS.kts b/.teamcity/patches/buildTypes/NetcoreTesting_CentOS.kts index 2cc6e24d..ba07b5da 100644 --- a/.teamcity/patches/buildTypes/NetcoreTesting_CentOS.kts +++ b/.teamcity/patches/buildTypes/NetcoreTesting_CentOS.kts @@ -21,10 +21,18 @@ changeBuildType(RelativeId("NetcoreTesting_CentOS")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/.teamcity/patches/buildTypes/NetcoreTesting_Debian.kts b/.teamcity/patches/buildTypes/NetcoreTesting_Debian.kts index c7312d6d..3c105042 100644 --- a/.teamcity/patches/buildTypes/NetcoreTesting_Debian.kts +++ b/.teamcity/patches/buildTypes/NetcoreTesting_Debian.kts @@ -21,10 +21,18 @@ changeBuildType(RelativeId("NetcoreTesting_Debian")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/.teamcity/patches/buildTypes/NetcoreTesting_Fedora.kts b/.teamcity/patches/buildTypes/NetcoreTesting_Fedora.kts index 8bc2bd08..13d8f6fb 100644 --- a/.teamcity/patches/buildTypes/NetcoreTesting_Fedora.kts +++ b/.teamcity/patches/buildTypes/NetcoreTesting_Fedora.kts @@ -21,10 +21,18 @@ changeBuildType(RelativeId("NetcoreTesting_Fedora")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/.teamcity/patches/buildTypes/NetcoreTesting_MacOsx.kts b/.teamcity/patches/buildTypes/NetcoreTesting_MacOsx.kts deleted file mode 100644 index cf893b4c..00000000 --- a/.teamcity/patches/buildTypes/NetcoreTesting_MacOsx.kts +++ /dev/null @@ -1,12 +0,0 @@ -package patches.buildTypes - -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.ui.* - -/* -This patch script was generated by TeamCity on settings change in UI. -To apply the patch, remove the buildType with id = 'NetcoreTesting_MacOsx' -from your code, and delete the patch script. -*/ -deleteBuildType(RelativeId("NetcoreTesting_MacOsx")) - diff --git a/.teamcity/patches/buildTypes/NetcoreTesting_OpenSUSE.kts b/.teamcity/patches/buildTypes/NetcoreTesting_OpenSUSE.kts index 1352569f..9af3d521 100644 --- a/.teamcity/patches/buildTypes/NetcoreTesting_OpenSUSE.kts +++ b/.teamcity/patches/buildTypes/NetcoreTesting_OpenSUSE.kts @@ -21,10 +21,18 @@ changeBuildType(RelativeId("NetcoreTesting_OpenSUSE")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/.teamcity/patches/buildTypes/NetcoreTesting_Rhel.kts b/.teamcity/patches/buildTypes/NetcoreTesting_Rhel.kts index 9407e46b..343fbd3e 100644 --- a/.teamcity/patches/buildTypes/NetcoreTesting_Rhel.kts +++ b/.teamcity/patches/buildTypes/NetcoreTesting_Rhel.kts @@ -21,10 +21,18 @@ changeBuildType(RelativeId("NetcoreTesting_Rhel")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/.teamcity/patches/buildTypes/NetcoreTesting_Sles.kts b/.teamcity/patches/buildTypes/NetcoreTesting_Sles.kts deleted file mode 100644 index 4e5e36fa..00000000 --- a/.teamcity/patches/buildTypes/NetcoreTesting_Sles.kts +++ /dev/null @@ -1,12 +0,0 @@ -package patches.buildTypes - -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.ui.* - -/* -This patch script was generated by TeamCity on settings change in UI. -To apply the patch, remove the buildType with id = 'NetcoreTesting_Sles' -from your code, and delete the patch script. -*/ -deleteBuildType(RelativeId("NetcoreTesting_Sles")) - diff --git a/.teamcity/patches/buildTypes/NetcoreTesting_Ubuntu.kts b/.teamcity/patches/buildTypes/NetcoreTesting_Ubuntu.kts index 33e68e40..c2c9066b 100644 --- a/.teamcity/patches/buildTypes/NetcoreTesting_Ubuntu.kts +++ b/.teamcity/patches/buildTypes/NetcoreTesting_Ubuntu.kts @@ -21,10 +21,18 @@ changeBuildType(RelativeId("NetcoreTesting_Ubuntu")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/.teamcity/patches/buildTypes/NetcoreTesting_Windows.kts b/.teamcity/patches/buildTypes/NetcoreTesting_Windows.kts index b5ef3df4..e56b1028 100644 --- a/.teamcity/patches/buildTypes/NetcoreTesting_Windows.kts +++ b/.teamcity/patches/buildTypes/NetcoreTesting_Windows.kts @@ -21,10 +21,18 @@ changeBuildType(RelativeId("NetcoreTesting_Windows")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/.teamcity/patches/buildTypes/PublishToFeedzIo.kts b/.teamcity/patches/buildTypes/PublishToFeedzIo.kts index d7cc4039..14f2919a 100644 --- a/.teamcity/patches/buildTypes/PublishToFeedzIo.kts +++ b/.teamcity/patches/buildTypes/PublishToFeedzIo.kts @@ -5,8 +5,6 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.CommitStatusPu import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.commitStatusPublisher import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.NuGetPublishStep import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.nuGetPublish -import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.VcsTrigger -import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.vcs import jetbrains.buildServer.configs.kotlin.v2019_2.ui.* /* @@ -15,10 +13,10 @@ To apply the patch, change the buildType with id = 'PublishToFeedzIo' accordingly, and delete the patch script. */ changeBuildType(RelativeId("PublishToFeedzIo")) { - check(name == "Publish to Feedz.io") { - "Unexpected name: '$name'" + check(description == "") { + "Unexpected description: '$description'" } - name = "Chain: Build and Test and Publish to Feedz.io" + description = "Build chain for Sashimi.AzureAppService" expectSteps { nuGetPublish { @@ -26,44 +24,26 @@ changeBuildType(RelativeId("PublishToFeedzIo")) { toolPath = "%teamcity.tool.NuGet.CommandLine.DEFAULT%" packages = "*.nupkg" serverUrl = "%InternalNuget.OctopusDependeciesFeedUrl%" - apiKey = "%nuGetPublish.apiKey%" + apiKey = "credentialsJSON:a7d4426a-7256-4df7-a953-266292e6ad81" args = "-Timeout 1200" } } steps { update(0) { clearConditions() - apiKey = "credentialsJSON:a7d4426a-7256-4df7-a953-266292e6ad81" param("org.jfrog.artifactory.selectedDeployableServer.downloadSpecSource", "Job configuration") param("org.jfrog.artifactory.selectedDeployableServer.useSpecs", "false") param("org.jfrog.artifactory.selectedDeployableServer.uploadSpecSource", "Job configuration") } } - triggers { - val trigger1 = find { - vcs { - branchFilter = """ - ## We actually want to publish all builds - +:refs/tags/* - +: - +:refs/heads/* - """.trimIndent() - } - } - trigger1.apply { - enabled = false - - } - } - features { val feature1 = find { commitStatusPublisher { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } @@ -72,161 +52,9 @@ changeBuildType(RelativeId("PublishToFeedzIo")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" } } } } - - dependencies { - expect(RelativeId("NetcoreTesting_AmazonLinux")) { - snapshot { - } - } - update(RelativeId("NetcoreTesting_AmazonLinux")) { - snapshot { - reuseBuilds = ReuseBuilds.ANY - } - } - - expect(RelativeId("NetcoreTesting_Ubuntu")) { - snapshot { - } - } - update(RelativeId("NetcoreTesting_Ubuntu")) { - snapshot { - reuseBuilds = ReuseBuilds.ANY - } - } - - expect(RelativeId("NetcoreTesting_OpenSUSE")) { - snapshot { - } - } - update(RelativeId("NetcoreTesting_OpenSUSE")) { - snapshot { - reuseBuilds = ReuseBuilds.ANY - } - } - - remove(RelativeId("NetcoreTesting_Sles")) { - snapshot { - } - } - - expect(RelativeId("NetcoreTesting_CentOS")) { - snapshot { - } - } - update(RelativeId("NetcoreTesting_CentOS")) { - snapshot { - reuseBuilds = ReuseBuilds.ANY - } - } - - expect(RelativeId("NetcoreTesting_Fedora")) { - snapshot { - } - } - update(RelativeId("NetcoreTesting_Fedora")) { - snapshot { - reuseBuilds = ReuseBuilds.ANY - } - } - - expect(RelativeId("NetcoreTesting_Debian")) { - snapshot { - } - } - update(RelativeId("NetcoreTesting_Debian")) { - snapshot { - reuseBuilds = ReuseBuilds.ANY - } - } - - expect(RelativeId("NetcoreTesting_Rhel")) { - snapshot { - } - } - update(RelativeId("NetcoreTesting_Rhel")) { - snapshot { - reuseBuilds = ReuseBuilds.ANY - } - } - - expect(RelativeId("NetcoreTesting_Windows")) { - snapshot { - } - } - update(RelativeId("NetcoreTesting_Windows")) { - snapshot { - reuseBuilds = ReuseBuilds.ANY - } - } - - remove(RelativeId("NetcoreTesting_MacOsx")) { - snapshot { - } - } - - remove(RelativeId("WindowsNetFxTesting_2008")) { - snapshot { - } - } - - remove(RelativeId("WindowsNetFxTesting_2008r2")) { - snapshot { - } - } - - expect(RelativeId("WindowsNetFxTesting_2012")) { - snapshot { - } - } - update(RelativeId("WindowsNetFxTesting_2012")) { - snapshot { - reuseBuilds = ReuseBuilds.ANY - } - } - - expect(RelativeId("WindowsNetFxTesting_2012r2")) { - snapshot { - } - } - update(RelativeId("WindowsNetFxTesting_2012r2")) { - snapshot { - reuseBuilds = ReuseBuilds.ANY - } - } - - expect(RelativeId("WindowsNetFxTesting_2016")) { - snapshot { - } - } - update(RelativeId("WindowsNetFxTesting_2016")) { - snapshot { - reuseBuilds = ReuseBuilds.ANY - } - } - - expect(RelativeId("WindowsNetFxTesting_2019")) { - snapshot { - } - } - update(RelativeId("WindowsNetFxTesting_2019")) { - snapshot { - reuseBuilds = ReuseBuilds.ANY - } - } - - } - - requirements { - remove { - equals("system.Octopus.Purpose", "Build") - } - add { - startsWith("system.agent.name", "nautilus-") - } - } } diff --git a/.teamcity/patches/buildTypes/WindowsNetFxTesting_2008.kts b/.teamcity/patches/buildTypes/WindowsNetFxTesting_2008.kts deleted file mode 100644 index a0b73399..00000000 --- a/.teamcity/patches/buildTypes/WindowsNetFxTesting_2008.kts +++ /dev/null @@ -1,12 +0,0 @@ -package patches.buildTypes - -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.ui.* - -/* -This patch script was generated by TeamCity on settings change in UI. -To apply the patch, remove the buildType with id = 'WindowsNetFxTesting_2008' -from your code, and delete the patch script. -*/ -deleteBuildType(RelativeId("WindowsNetFxTesting_2008")) - diff --git a/.teamcity/patches/buildTypes/WindowsNetFxTesting_2008r2.kts b/.teamcity/patches/buildTypes/WindowsNetFxTesting_2008r2.kts deleted file mode 100644 index 30d18d4c..00000000 --- a/.teamcity/patches/buildTypes/WindowsNetFxTesting_2008r2.kts +++ /dev/null @@ -1,12 +0,0 @@ -package patches.buildTypes - -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.ui.* - -/* -This patch script was generated by TeamCity on settings change in UI. -To apply the patch, remove the buildType with id = 'WindowsNetFxTesting_2008r2' -from your code, and delete the patch script. -*/ -deleteBuildType(RelativeId("WindowsNetFxTesting_2008r2")) - diff --git a/.teamcity/patches/buildTypes/WindowsNetFxTesting_2012.kts b/.teamcity/patches/buildTypes/WindowsNetFxTesting_2012.kts index a8fe4395..438d5e54 100644 --- a/.teamcity/patches/buildTypes/WindowsNetFxTesting_2012.kts +++ b/.teamcity/patches/buildTypes/WindowsNetFxTesting_2012.kts @@ -21,10 +21,18 @@ changeBuildType(RelativeId("WindowsNetFxTesting_2012")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/.teamcity/patches/buildTypes/WindowsNetFxTesting_2012r2.kts b/.teamcity/patches/buildTypes/WindowsNetFxTesting_2012r2.kts index c783bb6c..6afaf5dc 100644 --- a/.teamcity/patches/buildTypes/WindowsNetFxTesting_2012r2.kts +++ b/.teamcity/patches/buildTypes/WindowsNetFxTesting_2012r2.kts @@ -21,10 +21,18 @@ changeBuildType(RelativeId("WindowsNetFxTesting_2012r2")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/.teamcity/patches/buildTypes/WindowsNetFxTesting_2016.kts b/.teamcity/patches/buildTypes/WindowsNetFxTesting_2016.kts index ba68e618..3a26db69 100644 --- a/.teamcity/patches/buildTypes/WindowsNetFxTesting_2016.kts +++ b/.teamcity/patches/buildTypes/WindowsNetFxTesting_2016.kts @@ -21,10 +21,18 @@ changeBuildType(RelativeId("WindowsNetFxTesting_2016")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/.teamcity/patches/buildTypes/WindowsNetFxTesting_2019.kts b/.teamcity/patches/buildTypes/WindowsNetFxTesting_2019.kts index 2471c825..9d993b3c 100644 --- a/.teamcity/patches/buildTypes/WindowsNetFxTesting_2019.kts +++ b/.teamcity/patches/buildTypes/WindowsNetFxTesting_2019.kts @@ -21,10 +21,18 @@ changeBuildType(RelativeId("WindowsNetFxTesting_2019")) { publisher = github { githubUrl = "https://api.github.com" authType = personalToken { - token = "%commitStatusPublisher.apiKey%" + token = "credentialsJSON:d2d6ff31-56f1-4893-a448-f7a517da6c88" } } } } + feature1.apply { + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:7416c240-5c67-48ed-97a3-f5fe49d0e744" + } + } + } } } diff --git a/source/Calamari.Tests/AppServiceBehaviorFixture.cs b/source/Calamari.Tests/AppServiceBehaviorFixture.cs index 74733395..946c484a 100644 --- a/source/Calamari.Tests/AppServiceBehaviorFixture.cs +++ b/source/Calamari.Tests/AppServiceBehaviorFixture.cs @@ -1,15 +1,12 @@ - -using System; +using System.Collections.Generic; using System.IO; using System.IO.Compression; -using System.Net.Http; +using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; -using Azure.Identity; -using Azure.ResourceManager.Resources; using Azure.ResourceManager.Resources.Models; using Calamari.Azure; using Calamari.Common.Plumbing.FileSystem; @@ -17,279 +14,408 @@ using Calamari.Tests.Shared; using Calamari.Tests.Shared.LogParser; using FluentAssertions; +using Microsoft.Azure.Management.Storage; +using Microsoft.Azure.Management.Storage.Models; using Microsoft.Azure.Management.WebSites; using Microsoft.Azure.Management.WebSites.Models; using Microsoft.Rest; using NUnit.Framework; +using FileShare = System.IO.FileShare; +using Sku = Microsoft.Azure.Management.Storage.Models.Sku; +using StorageManagementClient = Microsoft.Azure.Management.Storage.StorageManagementClient; namespace Calamari.AzureAppService.Tests { - [TestFixture] public class AppServiceBehaviorFixture { - private string clientId; - private string clientSecret; - private string tenantId; - private string subscriptionId; - private string resourceGroupName; - private string authToken; - private string greeting = "Calamari"; - private ResourceGroupsOperations resourceGroupClient; - private WebSiteManagementClient webMgmtClient; - private Site site; - readonly HttpClient client = new HttpClient(); - - [OneTimeSetUp] - public async Task Setup() + [TestFixture] + public class WhenUsingAWindowsDotNetAppService : AppServiceIntegrationTest { - var resourceManagementEndpointBaseUri = - Environment.GetEnvironmentVariable(AccountVariables.ResourceManagementEndPoint) ?? - DefaultVariables.ResourceManagementEndpoint; - var activeDirectoryEndpointBaseUri = - Environment.GetEnvironmentVariable(AccountVariables.ActiveDirectoryEndPoint) ?? - DefaultVariables.ActiveDirectoryEndpoint; - - resourceGroupName = Guid.NewGuid().ToString(); - - clientId = ExternalVariables.Get(ExternalVariable.AzureSubscriptionClientId); - clientSecret = ExternalVariables.Get(ExternalVariable.AzureSubscriptionPassword); - tenantId = ExternalVariables.Get(ExternalVariable.AzureSubscriptionTenantId); - subscriptionId = ExternalVariables.Get(ExternalVariable.AzureSubscriptionId); - var resourceGroupLocation = Environment.GetEnvironmentVariable("AZURE_NEW_RESOURCE_REGION") ?? "eastus"; + private string servicePlanId; + protected override async Task ConfigureTestResources(ResourceGroup resourceGroup) + { + var svcPlan = await webMgmtClient.AppServicePlans.BeginCreateOrUpdateAsync( + resourceGroupName: resourceGroup.Name, + name: resourceGroup.Name, + new AppServicePlan(resourceGroup.Location) + { + Sku = new SkuDescription("S1", "Standard") + } + ); + + servicePlanId = svcPlan.Id; + + site = await webMgmtClient.WebApps.BeginCreateOrUpdateAsync( + resourceGroupName: resourceGroup.Name, + name: resourceGroup.Name, + new Site(resourceGroup.Location) + { + ServerFarmId = svcPlan.Id + } + ); + } - authToken = await Auth.GetAuthTokenAsync(activeDirectoryEndpointBaseUri, resourceManagementEndpointBaseUri, - tenantId, clientId, clientSecret); + [Test] + public async Task CanDeployWebAppZip() + { + var packageInfo = PrepareZipPackage(); + + await CommandTestBuilder.CreateAsync().WithArrange(context => + { + context.WithPackage(packageInfo.packagePath, packageInfo.packageName, packageInfo.packageVersion); + AddVariables(context); + }).Execute(); - var resourcesClient = new ResourcesManagementClient(subscriptionId, - new ClientSecretCredential(tenantId, clientId, clientSecret)); + await AssertContent($"{site.Name}.azurewebsites.net", $"Hello {greeting}"); + } + + [Test] + public async Task CanDeployWebAppZip_WithAzureCloudEnvironment() + { + var packageinfo = PrepareZipPackage(); - resourceGroupClient = resourcesClient.ResourceGroups; + await CommandTestBuilder.CreateAsync().WithArrange(context => + { + context.WithPackage(packageinfo.packagePath, packageinfo.packageName, packageinfo.packageVersion); + AddVariables(context); + context.AddVariable(AccountVariables.Environment, "AzureCloud"); + }).Execute(); + + await AssertContent($"{site.Name}.azurewebsites.net", $"Hello {greeting}"); + } + + [Test] + public async Task CanDeployWebAppZip_ToDeploymentSlot() + { + var slotName = "stage"; + greeting = "stage"; - var resourceGroup = new ResourceGroup(resourceGroupLocation); - resourceGroup = await resourceGroupClient.CreateOrUpdateAsync(resourceGroupName, resourceGroup); + (string packagePath, string packageName, string packageVersion) packageinfo; - webMgmtClient = new WebSiteManagementClient(new TokenCredentials(authToken)) - { - SubscriptionId = subscriptionId, - HttpClient = { BaseAddress = new Uri(DefaultVariables.ResourceManagementEndpoint) }, - }; + var slotTask = webMgmtClient.WebApps.BeginCreateOrUpdateSlotAsync(resourceGroupName, resourceGroupName, + site, + slotName); - var svcPlan = await webMgmtClient.AppServicePlans.BeginCreateOrUpdateAsync(resourceGroup.Name, - resourceGroup.Name, - new AppServicePlan(resourceGroup.Location) {Sku = new SkuDescription("S1", "Standard")} - ); + var tempPath = TemporaryDirectory.Create(); + new DirectoryInfo(tempPath.DirectoryPath).CreateSubdirectory("AzureZipDeployPackage"); + File.WriteAllText(Path.Combine($"{tempPath.DirectoryPath}/AzureZipDeployPackage", "index.html"), + "Hello #{Greeting}"); + packageinfo.packagePath = $"{tempPath.DirectoryPath}/AzureZipDeployPackage.1.0.0.zip"; + packageinfo.packageVersion = "1.0.0"; + packageinfo.packageName = "AzureZipDeployPackage"; + ZipFile.CreateFromDirectory($"{tempPath.DirectoryPath}/AzureZipDeployPackage", packageinfo.packagePath); - site = await webMgmtClient.WebApps.BeginCreateOrUpdateAsync(resourceGroup.Name, resourceGroup.Name, - new Site(resourceGroup.Location) { ServerFarmId = svcPlan.Id }); - } + await slotTask; - [OneTimeTearDown] - public async Task Cleanup() - { - await resourceGroupClient.StartDeleteAsync(resourceGroupName); - } + await CommandTestBuilder.CreateAsync().WithArrange(context => + { + context.WithPackage(packageinfo.packagePath, packageinfo.packageName, packageinfo.packageVersion); + AddVariables(context); + context.Variables.Add("Octopus.Action.Azure.DeploymentSlot", slotName); + }).Execute(); - [Test] - public async Task Deploy_WebAppZip() - { - var packageinfo = PrepareZipPackage(); + await AssertContent($"{site.Name}-{slotName}.azurewebsites.net", $"Hello {greeting}"); + } - await CommandTestBuilder.CreateAsync().WithArrange(context => + [Test] + public async Task CanDeployNugetPackage() { - context.WithPackage(packageinfo.packagePath, packageinfo.packageName, packageinfo.packageVersion); - AddVariables(context); - }).Execute(); + (string packagePath, string packageName, string packageVersion) packageinfo; + greeting = "nuget"; + + var tempPath = TemporaryDirectory.Create(); + new DirectoryInfo(tempPath.DirectoryPath).CreateSubdirectory("AzureZipDeployPackage"); + + var doc = new XDocument(new XElement("package", + new XAttribute("xmlns", @"http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"), + new XElement("metadata", + new XElement("id", "AzureZipDeployPackage"), + new XElement("version", "1.0.0"), + new XElement("title", "AzureZipDeployPackage"), + new XElement("authors","Chris Thomas"), + new XElement("description", "Test Package used to test nuget package deployments") + ) + )); + + await Task.Run(() => File.WriteAllText( + Path.Combine($"{tempPath.DirectoryPath}/AzureZipDeployPackage", "index.html"), + "Hello #{Greeting}")); + + using (var writer = new XmlTextWriter( + Path.Combine($"{tempPath.DirectoryPath}/AzureZipDeployPackage", "AzureZipDeployPackage.nuspec"), + Encoding.UTF8)) + { + doc.Save(writer); + } - await AssertContent($"{site.Name}.azurewebsites.net", $"Hello {greeting}"); - } - - [Test] - public async Task CanDeployZip_WithAzureCloudEnvironment() - { - var packageinfo = PrepareZipPackage(); + packageinfo.packagePath = $"{tempPath.DirectoryPath}/AzureZipDeployPackage.1.0.0.nupkg"; + packageinfo.packageVersion = "1.0.0"; + packageinfo.packageName = "AzureZipDeployPackage"; + ZipFile.CreateFromDirectory($"{tempPath.DirectoryPath}/AzureZipDeployPackage", packageinfo.packagePath); - await CommandTestBuilder.CreateAsync().WithArrange(context => - { - context.WithPackage(packageinfo.packagePath, packageinfo.packageName, packageinfo.packageVersion); - AddVariables(context); - context.AddVariable(AccountVariables.Environment, "AzureCloud"); - }).Execute(); - - await AssertContent($"{site.Name}.azurewebsites.net", $"Hello {greeting}"); - } - - [Test] - public async Task DeployingWithInvalidEnvironment_ThrowsAnException() - { - var packageinfo = PrepareZipPackage(); - - var commandResult = await CommandTestBuilder.CreateAsync().WithArrange(context => + await CommandTestBuilder.CreateAsync().WithArrange(context => { context.WithPackage(packageinfo.packagePath, packageinfo.packageName, packageinfo.packageVersion); AddVariables(context); - context.AddVariable(AccountVariables.Environment, "NonSenseEnvironment"); - }).Execute(false); + }).Execute(); - commandResult.Outcome.Should().Be(TestExecutionOutcome.Unsuccessful); - } - - private static (string packagePath, string packageName, string packageVersion) PrepareZipPackage() - { - (string packagePath, string packageName, string packageVersion) packageinfo; - - var tempPath = TemporaryDirectory.Create(); - new DirectoryInfo(tempPath.DirectoryPath).CreateSubdirectory("AzureZipDeployPackage"); - File.WriteAllText(Path.Combine($"{tempPath.DirectoryPath}/AzureZipDeployPackage", "index.html"), - "Hello #{Greeting}"); - - packageinfo.packagePath = $"{tempPath.DirectoryPath}/AzureZipDeployPackage.1.0.0.zip"; - packageinfo.packageVersion = "1.0.0"; - packageinfo.packageName = "AzureZipDeployPackage"; - ZipFile.CreateFromDirectory($"{tempPath.DirectoryPath}/AzureZipDeployPackage", packageinfo.packagePath); - return packageinfo; - } - - [Test] - public async Task Deploy_WebAppZipSlot() - { - var slotName = "stage"; - greeting = "stage"; - - (string packagePath, string packageName, string packageVersion) packageinfo; - - var slotTask = webMgmtClient.WebApps.BeginCreateOrUpdateSlotAsync(resourceGroupName, resourceGroupName, - site, - slotName); - - var tempPath = TemporaryDirectory.Create(); - new DirectoryInfo(tempPath.DirectoryPath).CreateSubdirectory("AzureZipDeployPackage"); - File.WriteAllText(Path.Combine($"{tempPath.DirectoryPath}/AzureZipDeployPackage", "index.html"), - "Hello #{Greeting}"); - packageinfo.packagePath = $"{tempPath.DirectoryPath}/AzureZipDeployPackage.1.0.0.zip"; - packageinfo.packageVersion = "1.0.0"; - packageinfo.packageName = "AzureZipDeployPackage"; - ZipFile.CreateFromDirectory($"{tempPath.DirectoryPath}/AzureZipDeployPackage", packageinfo.packagePath); - - await slotTask; + //await new AzureAppServiceBehaviour(new InMemoryLog()).Execute(runningContext); + await AssertContent($"{site.Name}.azurewebsites.net", $"Hello {greeting}"); + } + + [Test] + public async Task CanDeployWarPackage() + { + // Need to spin up a specific app service with Tomcat installed + // Need java installed on the test runner (MJH 2022-05-06: is this actually true? I don't see why we'd need java on the test runner) + var javaSite = await webMgmtClient.WebApps.BeginCreateOrUpdateAsync(resourceGroupName, + $"{resourceGroupName}-java", new Site(site.Location) + { + ServerFarmId = servicePlanId, + SiteConfig = new SiteConfig + { + JavaVersion = "1.8", + JavaContainer = "TOMCAT", + JavaContainerVersion = "9.0" + } + }); + + + (string packagePath, string packageName, string packageVersion) packageinfo; + var assemblyFileInfo = new FileInfo(Assembly.GetExecutingAssembly().Location); + packageinfo.packagePath = Path.Combine(assemblyFileInfo.Directory.FullName, "sample.1.0.0.war"); + packageinfo.packageVersion = "1.0.0"; + packageinfo.packageName = "sample"; + greeting = "java"; + + await CommandTestBuilder.CreateAsync().WithArrange(context => + { + context.WithPackage(packageinfo.packagePath, packageinfo.packageName, packageinfo.packageVersion); + AddVariables(context); + context.Variables["Octopus.Action.Azure.WebAppName"] = javaSite.Name; + context.Variables[PackageVariables.SubstituteInFilesTargets] = "test.jsp"; + }).Execute(); - await CommandTestBuilder.CreateAsync().WithArrange(context => + await AssertContent($"{javaSite.Name}.azurewebsites.net", $"Hello! {greeting}", "test.jsp"); + } + + [Test] + public async Task DeployingWithInvalidEnvironment_ThrowsAnException() { - context.WithPackage(packageinfo.packagePath, packageinfo.packageName, packageinfo.packageVersion); - AddVariables(context); - context.Variables.Add("Octopus.Action.Azure.DeploymentSlot", slotName); - }).Execute(); + var packageinfo = PrepareZipPackage(); + + var commandResult = await CommandTestBuilder.CreateAsync().WithArrange(context => + { + context.WithPackage(packageinfo.packagePath, packageinfo.packageName, packageinfo.packageVersion); + AddVariables(context); + context.AddVariable(AccountVariables.Environment, "NonSenseEnvironment"); + }).Execute(false); - await AssertContent($"{site.Name}-{slotName}.azurewebsites.net", $"Hello {greeting}"); - } + commandResult.Outcome.Should().Be(TestExecutionOutcome.Unsuccessful); + } - [Test] - public async Task Deploy_NugetPackage() - { - (string packagePath, string packageName, string packageVersion) packageinfo; - greeting = "nuget"; - - var tempPath = TemporaryDirectory.Create(); - new DirectoryInfo(tempPath.DirectoryPath).CreateSubdirectory("AzureZipDeployPackage"); - - var doc = new XDocument(new XElement("package", - new XAttribute("xmlns", @"http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"), - new XElement("metadata", - new XElement("id", "AzureZipDeployPackage"), - new XElement("version", "1.0.0"), - new XElement("title", "AzureZipDeployPackage"), - new XElement("authors","Chris Thomas"), - new XElement("description", "Test Package used to test nuget package deployments") - ) - )); - - await Task.Run(() => File.WriteAllText( - Path.Combine($"{tempPath.DirectoryPath}/AzureZipDeployPackage", "index.html"), - "Hello #{Greeting}")); - - using (var writer = new XmlTextWriter( - Path.Combine($"{tempPath.DirectoryPath}/AzureZipDeployPackage", "AzureZipDeployPackage.nuspec"), - Encoding.UTF8)) + [Test] + public async Task DeployToTwoTargetsInParallel_Succeeds() { - doc.Save(writer); + // Arrange + var packageInfo = PrepareFunctionAppZipPackage(); + // Without larger changes to Calamari and the Test Framework, it's not possible to run two Calamari + // processes in parallel in the same test method. Simulate the file locking behaviour by directly + // opening the affected file instead + var fileLock = File.Open(packageInfo.packagePath, FileMode.Open, FileAccess.Read, FileShare.Read); + + try + { + // Act + var deployment = await CommandTestBuilder.CreateAsync() + .WithArrange(context => + { + context.WithPackage(packageInfo.packagePath, packageInfo.packageName, + packageInfo.packageVersion); + AddVariables(context); + context.Variables[KnownVariables.Package.EnabledFeatures] = null; + }).Execute(); + + // Assert + deployment.Outcome.Should().Be(TestExecutionOutcome.Successful); + } + finally + { + fileLock.Close(); + } } - packageinfo.packagePath = $"{tempPath.DirectoryPath}/AzureZipDeployPackage.1.0.0.nupkg"; - packageinfo.packageVersion = "1.0.0"; - packageinfo.packageName = "AzureZipDeployPackage"; - ZipFile.CreateFromDirectory($"{tempPath.DirectoryPath}/AzureZipDeployPackage", packageinfo.packagePath); - - await CommandTestBuilder.CreateAsync().WithArrange(context => + private static (string packagePath, string packageName, string packageVersion) PrepareZipPackage() { - context.WithPackage(packageinfo.packagePath, packageinfo.packageName, packageinfo.packageVersion); - AddVariables(context); - }).Execute(); + (string packagePath, string packageName, string packageVersion) packageinfo; + + var tempPath = TemporaryDirectory.Create(); + new DirectoryInfo(tempPath.DirectoryPath).CreateSubdirectory("AzureZipDeployPackage"); + File.WriteAllText(Path.Combine($"{tempPath.DirectoryPath}/AzureZipDeployPackage", "index.html"), + "Hello #{Greeting}"); + + packageinfo.packagePath = $"{tempPath.DirectoryPath}/AzureZipDeployPackage.1.0.0.zip"; + packageinfo.packageVersion = "1.0.0"; + packageinfo.packageName = "AzureZipDeployPackage"; + ZipFile.CreateFromDirectory($"{tempPath.DirectoryPath}/AzureZipDeployPackage", packageinfo.packagePath); + return packageinfo; + } + + private static (string packagePath, string packageName, string packageVersion) PrepareFunctionAppZipPackage() + { + (string packagePath, string packageName, string packageVersion) packageInfo; + + var testAssemblyLocation = new FileInfo(Assembly.GetExecutingAssembly().Location); + var sourceZip = Path.Combine(testAssemblyLocation.Directory.FullName, "functionapp.1.0.0.zip"); + + packageInfo.packagePath = sourceZip; + packageInfo.packageVersion = "1.0.0"; + packageInfo.packageName = "functionapp"; + + return packageInfo; + } - //await new AzureAppServiceBehaviour(new InMemoryLog()).Execute(runningContext); - await AssertContent($"{site.Name}.azurewebsites.net", $"Hello {greeting}"); + private void AddVariables(CommandTestBuilderContext context) + { + AddAzureVariables(context); + context.Variables.Add("Greeting", greeting); + context.Variables.Add(KnownVariables.Package.EnabledFeatures, KnownVariables.Features.SubstituteInFiles); + context.Variables.Add(PackageVariables.SubstituteInFilesTargets, "index.html"); + context.Variables.Add(SpecialVariables.Action.Azure.DeploymentType, "ZipDeploy"); + } } - [Test] - public async Task Deploy_WarPackage() + [TestFixture] + public class WhenUsingALinuxAppService : AppServiceIntegrationTest { - // need to spin up java app service plan with a tomcat server - // need java installed on the test runner - var javaSvcPlan = await webMgmtClient.AppServicePlans.BeginCreateOrUpdateAsync(resourceGroupName, - $"{resourceGroupName}-java", new AppServicePlan(site.Location) - { - Sku = new SkuDescription { Name = "B1", Tier = "Basic" } - }); - - var javaSite = await webMgmtClient.WebApps.BeginCreateOrUpdateAsync(resourceGroupName, - $"{resourceGroupName}-java", new Site(site.Location) + protected override async Task ConfigureTestResources(ResourceGroup resourceGroup) + { + var storageClient = new StorageManagementClient(new TokenCredentials(authToken)) { - ServerFarmId = javaSvcPlan.Id, - SiteConfig = new SiteConfig + SubscriptionId = subscriptionId + }; + var storageAccountName = resourceGroupName.Replace("-", "").Substring(0, 20); + var storageAccount = await storageClient.StorageAccounts.CreateAsync(resourceGroupName, + accountName: storageAccountName, + new StorageAccountCreateParameters() { - JavaVersion = "1.8", - JavaContainer = "TOMCAT", - JavaContainerVersion = "9.0" + Sku = new Sku("Standard_LRS"), + Kind = "Storage", + Location = resourceGroupLocation } - }); - - - (string packagePath, string packageName, string packageVersion) packageinfo; - var assemblyFileInfo = new FileInfo(Assembly.GetExecutingAssembly().Location); - packageinfo.packagePath = Path.Combine(assemblyFileInfo.Directory.FullName, "sample.1.0.0.war"); - packageinfo.packageVersion = "1.0.0"; - packageinfo.packageName = "sample"; - greeting = "java"; + ); + + var keys = await storageClient.StorageAccounts.ListKeysAsync(resourceGroupName, storageAccountName); + + var linuxSvcPlan = await webMgmtClient.AppServicePlans.BeginCreateOrUpdateAsync(resourceGroupName, + $"{resourceGroupName}-linux-asp", + new AppServicePlan(resourceGroupLocation) + { + Sku = new SkuDescription("B1", "Basic"), + Kind = "linux", + Reserved = true + } + ); - await CommandTestBuilder.CreateAsync().WithArrange(context => + site = await webMgmtClient.WebApps.BeginCreateOrUpdateAsync(resourceGroupName, + $"{resourceGroupName}-linux", + new Site(resourceGroupLocation) + { + ServerFarmId = linuxSvcPlan.Id, + Kind = "functionapp,linux", + Reserved = true, + SiteConfig = new SiteConfig + { + AlwaysOn = true, + LinuxFxVersion = "DOTNET|6.0", + Use32BitWorkerProcess = true, + AppSettings = new List + { + new NameValuePair("FUNCTIONS_WORKER_RUNTIME", "dotnet"), + new NameValuePair("FUNCTIONS_EXTENSION_VERSION", "~4"), + new NameValuePair("AzureWebJobsStorage", $"DefaultEndpointsProtocol=https;AccountName={storageAccount.Name};AccountKey={keys.Keys.First().Value};EndpointSuffix=core.windows.net") + } + } + } + ); + } + + [Test] + public async Task CanDeployZip_ToLinuxFunctionApp() { - context.WithPackage(packageinfo.packagePath, packageinfo.packageName, packageinfo.packageVersion); - AddVariables(context); - context.Variables["Octopus.Action.Azure.WebAppName"] = javaSite.Name; - context.Variables[PackageVariables.SubstituteInFilesTargets] = "test.jsp"; - }).Execute(); + // Arrange + var packageInfo = PrepareZipPackage(); - await AssertContent($"{javaSite.Name}.azurewebsites.net", $"Hello! {greeting}", "test.jsp"); - - } + // Act + await CommandTestBuilder.CreateAsync().WithArrange(context => + { + context.WithPackage(packageInfo.packagePath, packageInfo.packageName, packageInfo.packageVersion); + AddVariables(context); + }).Execute(); + + // Assert + await DoWithRetries(10, async () => + { + await AssertContent($"{site.Name}.azurewebsites.net", + rootPath: $"api/HttpExample?name={greeting}", + actualText: $"Hello, {greeting}"); + }, + secondsBetweenRetries: 10); + } - private void AddVariables(CommandTestBuilderContext context) - { - context.Variables.Add(AccountVariables.ClientId, clientId); - context.Variables.Add(AccountVariables.Password, clientSecret); - context.Variables.Add(AccountVariables.TenantId, tenantId); - context.Variables.Add(AccountVariables.SubscriptionId, subscriptionId); - context.Variables.Add("Octopus.Action.Azure.ResourceGroupName", resourceGroupName); - context.Variables.Add("Octopus.Action.Azure.WebAppName", site.Name); - context.Variables.Add("Greeting", greeting); - context.Variables.Add(KnownVariables.Package.EnabledFeatures, KnownVariables.Features.SubstituteInFiles); - context.Variables.Add(PackageVariables.SubstituteInFilesTargets, "index.html"); - context.Variables.Add(SpecialVariables.Action.Azure.DeploymentType, "ZipDeploy"); - } + [Test] + public async Task CanDeployZip_ToLinuxFunctionApp_WithRunFromPackageFlag() + { + // Arrange + var settings = await webMgmtClient.WebApps.ListApplicationSettingsAsync(resourceGroupName, site.Name); + settings.Properties["WEBSITE_RUN_FROM_PACKAGE"] = "1"; + await webMgmtClient.WebApps.UpdateApplicationSettingsAsync(resourceGroupName, site.Name, settings); + + var packageInfo = PrepareZipPackage(); + + // Act + await CommandTestBuilder.CreateAsync().WithArrange(context => + { + context.WithPackage(packageInfo.packagePath, packageInfo.packageName, packageInfo.packageVersion); + AddVariables(context); + }).Execute(); + + // Assert + await DoWithRetries(10, async () => + { + await AssertContent($"{site.Name}.azurewebsites.net", + rootPath: $"api/HttpExample?name={greeting}", + actualText: $"Hello, {greeting}"); + }, + secondsBetweenRetries: 10); + } - async Task AssertContent(string hostName, string actualText, string rootPath = null) - { - var result = await client.GetStringAsync($"https://{hostName}/{rootPath}"); + private static (string packagePath, string packageName, string packageVersion) PrepareZipPackage() + { + // Looks like there's some file locking issues if multiple tests try to copy from the same file when running in parallel. + // For each test that needs one, create a temporary copy. + (string packagePath, string packageName, string packageVersion) packageInfo; + + var tempPath = TemporaryDirectory.Create(); + new DirectoryInfo(tempPath.DirectoryPath).CreateSubdirectory("AzureZipDeployPackage"); + + var testAssemblyLocation = new FileInfo(Assembly.GetExecutingAssembly().Location); + var sourceZip = Path.Combine(testAssemblyLocation.Directory.FullName, "functionapp.1.0.0.zip"); + var temporaryZipLocationForTest = $"{tempPath.DirectoryPath}/functionapp.1.0.0.zip"; + File.Copy(sourceZip, temporaryZipLocationForTest); + + packageInfo.packagePath = temporaryZipLocationForTest; + packageInfo.packageVersion = "1.0.0"; + packageInfo.packageName = "functionapp"; + + return packageInfo; + } - result.Should().Be(actualText); + private void AddVariables(CommandTestBuilderContext context) + { + AddAzureVariables(context); + context.Variables.Add(SpecialVariables.Action.Azure.DeploymentType, "ZipDeploy"); + } } } -} \ No newline at end of file +} diff --git a/source/Calamari.Tests/AppServiceIntegrationTest.cs b/source/Calamari.Tests/AppServiceIntegrationTest.cs new file mode 100644 index 00000000..c0d93769 --- /dev/null +++ b/source/Calamari.Tests/AppServiceIntegrationTest.cs @@ -0,0 +1,117 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Azure.Identity; +using Azure.ResourceManager.Resources; +using Azure.ResourceManager.Resources.Models; +using Calamari.Azure; +using Calamari.Tests.Shared; +using FluentAssertions; +using Microsoft.Azure.Management.WebSites; +using Microsoft.Azure.Management.WebSites.Models; +using Microsoft.Rest; +using NUnit.Framework; + +namespace Calamari.AzureAppService.Tests +{ + public abstract class AppServiceIntegrationTest + { + protected string clientId; + protected string clientSecret; + protected string tenantId; + protected string subscriptionId; + protected string resourceGroupName; + protected string resourceGroupLocation; + protected string greeting = "Calamari"; + protected string authToken; + protected WebSiteManagementClient webMgmtClient; + protected Site site; + + private ResourceGroupsOperations resourceGroupClient; + private readonly HttpClient client = new HttpClient(); + + [OneTimeSetUp] + public async Task Setup() + { + var resourceManagementEndpointBaseUri = + Environment.GetEnvironmentVariable(AccountVariables.ResourceManagementEndPoint) ?? + DefaultVariables.ResourceManagementEndpoint; + var activeDirectoryEndpointBaseUri = + Environment.GetEnvironmentVariable(AccountVariables.ActiveDirectoryEndPoint) ?? + DefaultVariables.ActiveDirectoryEndpoint; + + resourceGroupName = Guid.NewGuid().ToString(); + + clientId = ExternalVariables.Get(ExternalVariable.AzureSubscriptionClientId); + clientSecret = ExternalVariables.Get(ExternalVariable.AzureSubscriptionPassword); + tenantId = ExternalVariables.Get(ExternalVariable.AzureSubscriptionTenantId); + subscriptionId = ExternalVariables.Get(ExternalVariable.AzureSubscriptionId); + resourceGroupLocation = Environment.GetEnvironmentVariable("AZURE_NEW_RESOURCE_REGION") ?? "eastus"; + + authToken = await Auth.GetAuthTokenAsync(activeDirectoryEndpointBaseUri, resourceManagementEndpointBaseUri, + tenantId, clientId, clientSecret); + + var resourcesClient = new ResourcesManagementClient(subscriptionId, + new ClientSecretCredential(tenantId, clientId, clientSecret)); + + resourceGroupClient = resourcesClient.ResourceGroups; + + var resourceGroup = new ResourceGroup(resourceGroupLocation); + resourceGroup = await resourceGroupClient.CreateOrUpdateAsync(resourceGroupName, resourceGroup); + + webMgmtClient = new WebSiteManagementClient(new TokenCredentials(authToken)) + { + SubscriptionId = subscriptionId, + HttpClient = { BaseAddress = new Uri(DefaultVariables.ResourceManagementEndpoint) }, + }; + + await ConfigureTestResources(resourceGroup); + } + + protected abstract Task ConfigureTestResources(ResourceGroup resourceGroup); + + [OneTimeTearDown] + public async Task Cleanup() + { + if (resourceGroupClient != null) + await resourceGroupClient.StartDeleteAsync(resourceGroupName); + } + + protected async Task AssertContent(string hostName, string actualText, string rootPath = null) + { + var result = await client.GetStringAsync($"https://{hostName}/{rootPath}"); + + result.Should().Contain(actualText); + } + + protected static async Task DoWithRetries(int retries, Func action, int secondsBetweenRetries) + { + foreach (var retry in Enumerable.Range(1, retries)) + { + try + { + await action(); + break; + } + catch + { + if (retry == retries) + throw; + + await Task.Delay(secondsBetweenRetries * 1000); + } + } + } + + protected void AddAzureVariables(CommandTestBuilderContext context) + { + context.Variables.Add(AccountVariables.ClientId, clientId); + context.Variables.Add(AccountVariables.Password, clientSecret); + context.Variables.Add(AccountVariables.TenantId, tenantId); + context.Variables.Add(AccountVariables.SubscriptionId, subscriptionId); + context.Variables.Add("Octopus.Action.Azure.ResourceGroupName", resourceGroupName); + context.Variables.Add("Octopus.Action.Azure.WebAppName", site.Name); + } + } +} diff --git a/source/Calamari.Tests/AzureClientFixture.cs b/source/Calamari.Tests/AzureClientFixture.cs new file mode 100644 index 00000000..f55c6db4 --- /dev/null +++ b/source/Calamari.Tests/AzureClientFixture.cs @@ -0,0 +1,59 @@ +using System; +using Azure.ResourceManager; +using Calamari.Azure; +using NUnit.Framework; + +namespace Calamari.AzureAppService.Tests +{ + public class AzureClientFixture + { + [Test] + [TestCase("", "https://login.microsoftonline.com/")] + [TestCase("AzureCloud", "https://login.microsoftonline.com/")] + [TestCase("AzureGlobalCloud", "https://login.microsoftonline.com/")] + [TestCase("AzureChinaCloud", "https://login.chinacloudapi.cn/")] + [TestCase("AzureGermanCloud", "https://login.microsoftonline.de/")] + [TestCase("AzureUSGovernment", "https://login.microsoftonline.us/")] + public void AzureClientOptions_IdentityAuth_UsesCorrectEndpointsForRegions(string azureCloud, string expectedAuthorityHost) + { + // Arrange + var servicePrincipalAccount = GetAccountFor(azureCloud); + + // Act + var (_, tokenCredentialOptions) = servicePrincipalAccount.GetArmClientOptions(); + + // Assert + Assert.AreEqual(new Uri(expectedAuthorityHost), tokenCredentialOptions.AuthorityHost); + } + + [Test] + [TestCase("", "https://management.azure.com")] + [TestCase("AzureCloud", "https://management.azure.com")] + [TestCase("AzureGlobalCloud", "https://management.azure.com")] + [TestCase("AzureChinaCloud", "https://management.chinacloudapi.cn")] + [TestCase("AzureGermanCloud", "https://management.microsoftazure.de")] + [TestCase("AzureUSGovernment", "https://management.usgovcloudapi.net")] + public void AzureClientOptions_ApiCall_UsesCorrectEndpointsForRegions(string azureCloud, string expectedManagementEndpoint) + { + // Arrange + var servicePrincipalAccount = GetAccountFor(azureCloud); + + // Act + var (armClientOptions, _) = servicePrincipalAccount.GetArmClientOptions(); + + // Assert + Assert.AreEqual(new Uri(expectedManagementEndpoint), armClientOptions.Environment.Value.Endpoint); + } + + private ServicePrincipalAccount GetAccountFor(string azureCloud) + { + return new ServicePrincipalAccount("123-456-789", + "clientId", + "tenantId", + "p@ssw0rd", + azureCloud, + null, + null); + } + } +} \ No newline at end of file diff --git a/source/Calamari.Tests/AzureWebAppHelperFixture.cs b/source/Calamari.Tests/AzureWebAppHelperFixture.cs index 6614c190..94c9c541 100644 --- a/source/Calamari.Tests/AzureWebAppHelperFixture.cs +++ b/source/Calamari.Tests/AzureWebAppHelperFixture.cs @@ -1,14 +1,8 @@ using Calamari.Azure; using FluentAssertions; using FluentAssertions.Execution; -using Microsoft.Azure.Management.AppService.Fluent; -using NSubstitute; using NUnit.Framework; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Calamari.AzureAppService.Tests { @@ -27,11 +21,9 @@ public void GetOctopusTags_FindsMatchingTags_RegardlessOfCase() { "oCtoPus-sPacE", "taggedSpace" }, { "ocTopUs-teNanT", "taggedTenant" }, }; - var webApp = Substitute.For(); - webApp.Tags.Returns(tags); // Act - var foundTags = AzureWebAppHelper.GetOctopusTags(webApp); + var foundTags = AzureWebAppHelper.GetOctopusTags(tags); // Assert using (new AssertionScope()) diff --git a/source/Calamari.Tests/Calamari.Tests.csproj b/source/Calamari.Tests/Calamari.Tests.csproj index 58513b13..9bf75dee 100644 --- a/source/Calamari.Tests/Calamari.Tests.csproj +++ b/source/Calamari.Tests/Calamari.Tests.csproj @@ -6,7 +6,6 @@ false win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 8.0 - $(MSBuildProjectDirectory)\testSettings.runsettings net461;net5.0 @@ -20,6 +19,8 @@ + + @@ -32,5 +33,8 @@ Always + + Always + diff --git a/source/Calamari.Tests/TargetDiscoveryBehaviourIntegrationTestFixture.cs b/source/Calamari.Tests/TargetDiscoveryBehaviourIntegrationTestFixture.cs index 217f5ef5..71d3a79f 100644 --- a/source/Calamari.Tests/TargetDiscoveryBehaviourIntegrationTestFixture.cs +++ b/source/Calamari.Tests/TargetDiscoveryBehaviourIntegrationTestFixture.cs @@ -33,7 +33,13 @@ public class TargetDiscoveryBehaviourIntegrationTestFixture private string authToken; private ResourceGroupsOperations resourceGroupClient; private WebSiteManagementClient webMgmtClient; + private ResourceGroup resourceGroup; + private AppServicePlan svcPlan; private string appName = Guid.NewGuid().ToString(); + private List slotNames = new List { "blue", "green" }; + private static readonly string AccountId = "Accounts-1"; + private static readonly string Role = "my-azure-app-role"; + private static readonly string EnvironmentName = "dev"; [OneTimeSetUp] public async Task Setup() @@ -61,53 +67,214 @@ public async Task Setup() resourceGroupClient = resourcesClient.ResourceGroups; - var resourceGroup = new ResourceGroup(resourceGroupLocation); + resourceGroup = new ResourceGroup(resourceGroupLocation); resourceGroup = await resourceGroupClient.CreateOrUpdateAsync(resourceGroupName, resourceGroup); webMgmtClient = new WebSiteManagementClient(new TokenCredentials(authToken)) { SubscriptionId = subscriptionId, - HttpClient = { BaseAddress = new Uri(DefaultVariables.ResourceManagementEndpoint) }, + HttpClient = { BaseAddress = new Uri(DefaultVariables.ResourceManagementEndpoint), Timeout = TimeSpan.FromMinutes(5) }, }; - var svcPlan = await webMgmtClient.AppServicePlans.BeginCreateOrUpdateAsync(resourceGroup.Name, + svcPlan = await webMgmtClient.AppServicePlans.CreateOrUpdateAsync(resourceGroup.Name, resourceGroup.Name, new AppServicePlan(resourceGroup.Location) { Sku = new SkuDescription("S1", "Standard") } ); + } + + [OneTimeTearDown] + public async Task Cleanup() + { + if (resourceGroupClient != null) + await resourceGroupClient.StartDeleteAsync(resourceGroupName); + } + + [SetUp] + public async Task CreateOrResetWebAppAndSlots() + { + // Call update on the web app and each slot without and tags + // to reset it for each test. + await CreateOrUpdateTestWebApp(); + await CreateOrUpdateTestWebAppSlots(); + } + [Test] + public async Task Execute_WebAppWithMatchingTags_CreatesCorrectTargets() + { + // Arrange + var variables = new CalamariVariables(); + var context = new RunningDeployment(variables); + this.CreateVariables(context); + var log = new InMemoryLog(); + var sut = new TargetDiscoveryBehaviour(log); + + // Set expected tags on our web app var tags = new Dictionary { - { TargetTags.EnvironmentTagName, "dev" }, - { TargetTags.RoleTagName, "my-azure-app-role" }, + { TargetTags.EnvironmentTagName, EnvironmentName }, + { TargetTags.RoleTagName, Role }, }; - await webMgmtClient.WebApps.BeginCreateOrUpdateAsync( - resourceGroup.Name, - appName, - new Site(resourceGroup.Location, tags: tags) { ServerFarmId = svcPlan.Id }); + + await CreateOrUpdateTestWebApp(tags); + + // Act + await sut.Execute(context); + + // Assert + var serviceMessageToCreateWebAppTarget = TargetDiscoveryHelpers.CreateWebAppTargetCreationServiceMessage(resourceGroupName, appName, AccountId, Role, null); + log.StandardOut.Should().Contain(serviceMessageToCreateWebAppTarget.ToString()); } - [OneTimeTearDown] - public async Task Cleanup() + [Test] + public async Task Execute_WebAppWithNonMatchingTags_CreatesNoTargets() { - await resourceGroupClient.StartDeleteAsync(resourceGroupName); + // Arrange + var variables = new CalamariVariables(); + var context = new RunningDeployment(variables); + this.CreateVariables(context); + var log = new InMemoryLog(); + var sut = new TargetDiscoveryBehaviour(log); + + // Set expected tags on our web app + var tags = new Dictionary + { + { TargetTags.EnvironmentTagName, EnvironmentName }, + { TargetTags.RoleTagName, "a-different-role" }, + }; + + await CreateOrUpdateTestWebApp(tags); + + // Act + await sut.Execute(context); + + // Assert + var serviceMessageToCreateWebAppTarget = TargetDiscoveryHelpers.CreateWebAppTargetCreationServiceMessage(resourceGroupName, appName, AccountId, Role, null); + log.StandardOut.Should().NotContain(serviceMessageToCreateWebAppTarget.ToString(), "The web app target should not be created as the role tag did not match"); } [Test] - public async Task Exectute_FindsWebApp_WhenOneExistsWithCorrectTags() + public async Task Execute_MultipleWebAppSlotsWithTags_WebAppHasNoTags_CreatesCorrectTargets() { // Arrange var variables = new CalamariVariables(); var context = new RunningDeployment(variables); - this.CreateVariables(context); + CreateVariables(context); var log = new InMemoryLog(); var sut = new TargetDiscoveryBehaviour(log); + // Set expected tags on each slot of the web app but not the web app itself + var tags = new Dictionary + { + { TargetTags.EnvironmentTagName, EnvironmentName }, + { TargetTags.RoleTagName, Role }, + }; + + await CreateOrUpdateTestWebAppSlots(tags); + // Act await sut.Execute(context); + var serviceMessageToCreateWebAppTarget = TargetDiscoveryHelpers.CreateWebAppTargetCreationServiceMessage(resourceGroupName, appName, AccountId, Role, null); + log.StandardOut.Should().NotContain(serviceMessageToCreateWebAppTarget.ToString(), "A target should not be created for the web app itself, only for slots within the web app"); + // Assert - var expectedName = Convert.ToBase64String(Encoding.UTF8.GetBytes(appName)); - log.StandardOut.Should().Contain(line => line.StartsWith($"##octopus[create-azurewebapptarget name=\"{expectedName}\"")); + foreach (var slotName in slotNames) + { + var serviceMessageToCreateTargetForSlot = TargetDiscoveryHelpers.CreateWebAppDeploymentSlotTargetCreationServiceMessage(resourceGroupName, appName, slotName, AccountId, Role, null); + log.StandardOut.Should().Contain(serviceMessageToCreateTargetForSlot.ToString()); + } + } + + [Test] + public async Task Execute_MultipleWebAppSlotsWithTags_WebAppWithTags_CreatesCorrectTargets() + { + // Arrange + var variables = new CalamariVariables(); + var context = new RunningDeployment(variables); + CreateVariables(context); + var log = new InMemoryLog(); + var sut = new TargetDiscoveryBehaviour(log); + + // Set expected tags on each slot of the web app AND the web app itself + var tags = new Dictionary + { + { TargetTags.EnvironmentTagName, EnvironmentName }, + { TargetTags.RoleTagName, Role }, + }; + + await CreateOrUpdateTestWebApp(tags); + await CreateOrUpdateTestWebAppSlots(tags); + + // Act + await sut.Execute(context); + + var serviceMessageToCreateWebAppTarget = TargetDiscoveryHelpers.CreateWebAppTargetCreationServiceMessage(resourceGroupName, appName, AccountId, Role, null); + log.StandardOut.Should().Contain(serviceMessageToCreateWebAppTarget.ToString(), "A target should be created for the web app itself as well as for the slots"); + + // Assert + foreach (var slotName in slotNames) + { + var serviceMessageToCreateTargetForSlot = TargetDiscoveryHelpers.CreateWebAppDeploymentSlotTargetCreationServiceMessage(resourceGroupName, appName, slotName, AccountId, Role, null); + log.StandardOut.Should().Contain(serviceMessageToCreateTargetForSlot.ToString()); + } + } + + [Test] + public async Task Execute_MultipleWebAppSlotsWithPartialTags_WebAppWithPartialTags_CreatesNoTargets() + { + // Arrange + var variables = new CalamariVariables(); + var context = new RunningDeployment(variables); + CreateVariables(context); + var log = new InMemoryLog(); + var sut = new TargetDiscoveryBehaviour(log); + + // Set partial tags on each slot of the web app AND the remaining ones on the web app itself + var webAppTags = new Dictionary + { + { TargetTags.EnvironmentTagName, EnvironmentName }, + }; + + var slotTags = new Dictionary + { + { TargetTags.RoleTagName, Role }, + }; + + await CreateOrUpdateTestWebApp(webAppTags); + await CreateOrUpdateTestWebAppSlots(slotTags); + + // Act + await sut.Execute(context); + + var serviceMessageToCreateWebAppTarget = TargetDiscoveryHelpers.CreateWebAppTargetCreationServiceMessage(resourceGroupName, appName, AccountId, Role, null); + log.StandardOut.Should().NotContain(serviceMessageToCreateWebAppTarget.ToString(), "A target should not be created for the web app as the tags directly on the web app do not match, even though when combined with the slot tags they do"); + + // Assert + foreach (var slotName in slotNames) + { + var serviceMessageToCreateTargetForSlot = TargetDiscoveryHelpers.CreateWebAppDeploymentSlotTargetCreationServiceMessage(resourceGroupName, appName, slotName, AccountId, Role, null); + log.StandardOut.Should().NotContain(serviceMessageToCreateTargetForSlot.ToString(), "A target should not be created for the web app slot as the tags directly on the slot do not match, even though when combined with the web app tags they do"); + } + } + + private async Task CreateOrUpdateTestWebApp(Dictionary tags = null) + { + await webMgmtClient.WebApps.CreateOrUpdateAsync( + resourceGroupName, + appName, + new Site(resourceGroup.Location, tags: tags) { ServerFarmId = svcPlan.Id }); + } + + private async Task CreateOrUpdateTestWebAppSlots(Dictionary tags = null) + { + foreach (var slotName in slotNames) + { + await webMgmtClient.WebApps.CreateOrUpdateSlotAsync( + resourceGroup.Name, + appName, + new Site(resourceGroup.Location, tags: tags) { ServerFarmId = svcPlan.Id }, + slotName); + } } private void CreateVariables(RunningDeployment context) @@ -115,13 +282,13 @@ private void CreateVariables(RunningDeployment context) string targetDiscoveryContext = $@"{{ ""scope"": {{ ""spaceName"": ""default"", - ""environmentName"": ""dev"", + ""environmentName"": ""{EnvironmentName}"", ""projectName"": ""my-test-project"", ""tenantName"": null, - ""roles"": [""my-azure-app-role""] + ""roles"": [""{Role}""] }}, ""authentication"": {{ - ""accountId"": ""Accounts-1"", + ""accountId"": ""{AccountId}"", ""accountDetails"": {{ ""subscriptionNumber"": ""{subscriptionId}"", ""clientId"": ""{clientId}"", diff --git a/source/Calamari.Tests/functionapp.1.0.0.zip b/source/Calamari.Tests/functionapp.1.0.0.zip new file mode 100644 index 00000000..b7e9b8c0 Binary files /dev/null and b/source/Calamari.Tests/functionapp.1.0.0.zip differ diff --git a/source/Calamari.Tests/testSettings.runsettings b/source/Calamari.Tests/testSettings.runsettings deleted file mode 100644 index 85f1cef4..00000000 --- a/source/Calamari.Tests/testSettings.runsettings +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/source/Calamari/Azure/AzureClient.cs b/source/Calamari/Azure/AzureClient.cs index 3d208bfc..437ebe09 100644 --- a/source/Calamari/Azure/AzureClient.cs +++ b/source/Calamari/Azure/AzureClient.cs @@ -35,15 +35,35 @@ public static IAzure CreateAzureClient(this ServicePrincipalAccount servicePrinc /// public static ArmClient CreateArmClient(this ServicePrincipalAccount servicePrincipal) { - var environment = new AzureKnownEnvironment(servicePrincipal.AzureEnvironment).AsAzureArmEnvironment(); + var (armClientOptions, tokenCredentialOptions) = GetArmClientOptions(servicePrincipal); + var credential = new ClientSecretCredential(servicePrincipal.TenantId, servicePrincipal.ClientId, servicePrincipal.Password, tokenCredentialOptions); + return new ArmClient(credential, defaultSubscriptionId: servicePrincipal.SubscriptionNumber, armClientOptions); + } + + public static (ArmClientOptions, TokenCredentialOptions) GetArmClientOptions(this ServicePrincipalAccount servicePrincipalAccount) + { + var azureKnownEnvironment = new AzureKnownEnvironment(servicePrincipalAccount.AzureEnvironment); + // Configure a specific transport that will pick up the proxy settings set by Calamari var httpClientTransport = new HttpClientTransport(new HttpClientHandler { Proxy = WebRequest.DefaultWebProxy }); - var tokenCredentialOptions = new TokenCredentialOptions { Transport = httpClientTransport }; - var credential = new ClientSecretCredential(servicePrincipal.TenantId, servicePrincipal.ClientId, servicePrincipal.Password, tokenCredentialOptions); + // Specifically tell the new Azure SDK which authentication endpoint to use + var authorityHost = azureKnownEnvironment.GetAzureAuthorityHost(); + var tokenCredentialOptions = new TokenCredentialOptions + { + Transport = httpClientTransport, + AuthorityHost = authorityHost + }; - var armClientOptions = new ArmClientOptions() { Transport = httpClientTransport, Environment = environment }; - return new ArmClient(credential, defaultSubscriptionId: servicePrincipal.SubscriptionNumber, armClientOptions); + // The new Azure SDK uses a different representation of Environments + var armEnvironment = azureKnownEnvironment.AsAzureArmEnvironment(); + var armClientOptions = new ArmClientOptions() + { + Transport = httpClientTransport, + Environment = armEnvironment + }; + + return (armClientOptions, tokenCredentialOptions); } } } diff --git a/source/Calamari/Azure/AzureKnownEnvironment.cs b/source/Calamari/Azure/AzureKnownEnvironment.cs index 5fdd1212..21ef4066 100644 --- a/source/Calamari/Azure/AzureKnownEnvironment.cs +++ b/source/Calamari/Azure/AzureKnownEnvironment.cs @@ -1,4 +1,5 @@ using System; +using Azure.Identity; using Azure.ResourceManager; using Microsoft.Azure.Management.ResourceManager.Fluent; @@ -43,5 +44,16 @@ public AzureEnvironment AsAzureSDKEnvironment() "AzureUSGovernment" => ArmEnvironment.AzureGovernment, _ => throw new InvalidOperationException($"ARM Environment {name} is not a known Azure Environment name.") }; + + public Uri GetAzureAuthorityHost() => ToAzureAuthorityHost(Value); + + private static Uri ToAzureAuthorityHost(string name) => name switch + { + "AzureGlobalCloud" => AzureAuthorityHosts.AzurePublicCloud, + "AzureChinaCloud" => AzureAuthorityHosts.AzureChina, + "AzureGermanCloud" => AzureAuthorityHosts.AzureGermany, + "AzureUSGovernment" => AzureAuthorityHosts.AzureGovernment, + _ => throw new InvalidOperationException($"ARM Environment {name} is not a known Azure Environment name.") + }; } } diff --git a/source/Calamari/Azure/AzureWebAppHelper.cs b/source/Calamari/Azure/AzureWebAppHelper.cs index 92205aa4..0b8aafaf 100644 --- a/source/Calamari/Azure/AzureWebAppHelper.cs +++ b/source/Calamari/Azure/AzureWebAppHelper.cs @@ -37,9 +37,9 @@ public static TargetSite GetAzureTargetSite(string siteAndMaybeSlotName, string? return targetSite; } - public static TargetTags GetOctopusTags(IWebAppBasic webApp) + public static TargetTags GetOctopusTags(IReadOnlyDictionary tags) { - var caseInsensitiveTagDictionary = webApp.Tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase); + var caseInsensitiveTagDictionary = tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase); caseInsensitiveTagDictionary.TryGetValue(TargetTags.EnvironmentTagName, out string? environment); caseInsensitiveTagDictionary.TryGetValue(TargetTags.RoleTagName, out string? role); caseInsensitiveTagDictionary.TryGetValue(TargetTags.ProjectTagName, out string? project); diff --git a/source/Calamari/Behaviors/AzureAppServiceBehaviour.cs b/source/Calamari/Behaviors/AzureAppServiceBehaviour.cs index 257db71b..12206d9e 100644 --- a/source/Calamari/Behaviors/AzureAppServiceBehaviour.cs +++ b/source/Calamari/Behaviors/AzureAppServiceBehaviour.cs @@ -150,9 +150,14 @@ private async Task UploadZipAsync(PublishingProfile publishingProfile, HttpClien client.Timeout = TimeSpan.FromHours(1); var response = await client.PostAsync($@"{publishingProfile.PublishUrl}{Archive.UploadUrlPath}", - new StreamContent(new FileStream(uploadZipPath, FileMode.Open))); - - if (!response.IsSuccessStatusCode) throw new Exception(response.ReasonPhrase); + new StreamContent(new FileStream(uploadZipPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + Headers = { ContentType = new MediaTypeHeaderValue("application/octet-stream") } + } + ); + + if (!response.IsSuccessStatusCode) + throw new Exception(response.ReasonPhrase); Log.Verbose("Finished deploying"); } diff --git a/source/Calamari/Behaviors/TargetDiscoveryBehaviour.cs b/source/Calamari/Behaviors/TargetDiscoveryBehaviour.cs index 9391eec7..8aa501eb 100644 --- a/source/Calamari/Behaviors/TargetDiscoveryBehaviour.cs +++ b/source/Calamari/Behaviors/TargetDiscoveryBehaviour.cs @@ -12,6 +12,9 @@ using Calamari.Common.Plumbing.ServiceMessages; using Calamari.Common.Plumbing.Variables; using Microsoft.Azure.Management.AppService.Fluent; +using Microsoft.Azure.Management.AppService.Fluent.Models; +using Microsoft.Azure.Management.Fluent; +using Polly; #nullable enable namespace Calamari.AzureAppService.Behaviors @@ -46,18 +49,18 @@ public async Task Execute(RunningDeployment runningDeployment) try { var discoveredTargetCount = 0; - var webApps = azureClient.WebApps.ListWebAppBasic(); + var webApps = ListWebApps(azureClient); Log.Verbose($"Found {webApps.Count()} candidate web apps."); foreach (var webApp in webApps) { - var tags = AzureWebAppHelper.GetOctopusTags(webApp); + var tags = AzureWebAppHelper.GetOctopusTags(webApp.Tags); var matchResult = targetDiscoveryContext.Scope.Match(tags); if (matchResult.IsSuccess) { discoveredTargetCount++; Log.Info($"Discovered matching web app: {webApp.Name}"); WriteTargetCreationServiceMessage( - webApp, targetDiscoveryContext, matchResult, runningDeployment.Variables); + webApp, targetDiscoveryContext, matchResult); } else { @@ -67,6 +70,31 @@ public async Task Execute(RunningDeployment runningDeployment) Log.Verbose($"- {reason}"); } } + + Log.Verbose($"Looking for deployment slots in web app {webApp.Name}"); + + var deploymentSlots = ListDeploymentSlots(webApp); + + foreach (var slot in deploymentSlots) + { + var deploymentSlotTags = AzureWebAppHelper.GetOctopusTags(slot.Tags); + var deploymentSlotMatchResult = targetDiscoveryContext.Scope.Match(deploymentSlotTags); + if (deploymentSlotMatchResult.IsSuccess) + { + discoveredTargetCount++; + Log.Info($"Discovered matching deployment slot {slot.Name} in web app {webApp.Name}"); + WriteDeploymentSlotTargetCreationServiceMessage( + webApp, slot, targetDiscoveryContext, deploymentSlotMatchResult); + } + else + { + Log.Verbose($"Deployment slot {slot.Name} in web app {webApp.Name} does not match target requirements:"); + foreach (var reason in matchResult.FailureReasons) + { + Log.Verbose($"- {reason}"); + } + } + } } if (discoveredTargetCount > 0) @@ -86,28 +114,90 @@ public async Task Execute(RunningDeployment runningDeployment) } } + private IEnumerable ListWebApps(IAzure azureClient) + { + var retryPolicy = CreateAzureQueryRetryPolicy(5, $"listing web apps"); + + return retryPolicy.Execute(() => + { + return azureClient.WebApps.List(); + }); + } + + private ISyncPolicy CreateAzureQueryRetryPolicy(int maxRetries, string description) + { + // Don't bother retrying for not found errors as we will only ever get the same response + return Policy + .Handle() + .WaitAndRetry( + maxRetries, + (retryAttempt, ex, context) => + { + if (ex is DefaultErrorResponseException dex) + { + // Need to cast to an int here as net461 doesn't have TooManyRequests in the enum + if ((int)dex.Response.StatusCode == 429 && dex.Response.Headers.TryGetValue("Retry-After", out var retryAfter)) + { + return TimeSpan.FromSeconds(int.Parse(retryAfter.First())); + } + } + // Not a specific throttling exception, use exponential backoff with a maximum wait time of 10 seconds + return TimeSpan.FromSeconds(Math.Min(10, Math.Pow(2, retryAttempt))); + }, + (ex, delay, retryAttempt, context) => + { + Log.Verbose($"An error has occurred {description}: {ex.Message}, retrying {retryAttempt} of {maxRetries} after {delay}"); + }); + } + + private IEnumerable ListDeploymentSlots(IWebApp webApp) + { + var retryPolicy = CreateAzureQueryRetryPolicy(5, $"listing deployment slots for web app {webApp.Name}"); + + // We could get a 404 listing slots if the web app gets deleted + // after being found but before we can check it's slots, in this + // case we'll log a message and continue + var webAppNotFoundPolicy = Policy> + .Handle(dex => dex.Response.StatusCode == System.Net.HttpStatusCode.NotFound) + .Fallback( + fallbackValue: Enumerable.Empty(), + onFallback: (exception, context) => Log.Verbose($"Could not list deployment slots for web app {webApp.Name} as it could no longer be found") + ); + + return retryPolicy.Wrap(webAppNotFoundPolicy).Execute(() => + { + return webApp.DeploymentSlots.List(); + }); + } + private void WriteTargetCreationServiceMessage( - IWebAppBasic webApp, + IWebApp webApp, TargetDiscoveryContext> context, - TargetMatchResult matchResult, - IVariables variables) + TargetMatchResult matchResult) { - // TODO: handle web app slots. - var parameters = new Dictionary { - { "name", webApp.Name }, - { "azureWebApp", webApp.Name }, - { "azureResourceGroupName", webApp.ResourceGroupName }, - { "octopusAccountIdOrName", context.Authentication.AccountId }, - { "octopusRoles", matchResult.Role }, - { "updateIfExisting", "True" }, - { "octopusDefaultWorkerPoolIdOrName", context.Scope!.WorkerPoolId }, - { "isDynamic", "True" } - }; + Log.WriteServiceMessage( + TargetDiscoveryHelpers.CreateWebAppTargetCreationServiceMessage( + webApp.ResourceGroupName, + webApp.Name, + context.Authentication.AccountId, + matchResult.Role, + context.Scope!.WorkerPoolId)); + } - var serviceMessage = new ServiceMessage( - "create-azurewebapptarget", - parameters.Where(p => p.Value != null).ToDictionary, string, string>(p => p.Key, p => p.Value!)); - Log.WriteServiceMessage(serviceMessage); + private void WriteDeploymentSlotTargetCreationServiceMessage( + IWebApp webApp, + IDeploymentSlot slot, + TargetDiscoveryContext> context, + TargetMatchResult matchResult) + { + Log.WriteServiceMessage( + TargetDiscoveryHelpers.CreateWebAppDeploymentSlotTargetCreationServiceMessage( + webApp.ResourceGroupName, + webApp.Name, + slot.Name, + context.Authentication.AccountId, + matchResult.Role, + context.Scope!.WorkerPoolId)); } private TargetDiscoveryContext>? GetTargetDiscoveryContext( @@ -139,4 +229,44 @@ private void WriteTargetCreationServiceMessage( } } } + + public static class TargetDiscoveryHelpers + { + public static ServiceMessage CreateWebAppTargetCreationServiceMessage(string resourceGroupName, string webAppName, string accountId, string role, string? workerPoolId) + { + var parameters = new Dictionary { + { "name", $"azure-web-app/{resourceGroupName}/{webAppName}" }, + { "azureWebApp", webAppName }, + { "azureResourceGroupName", resourceGroupName }, + { "octopusAccountIdOrName", accountId }, + { "octopusRoles", role }, + { "updateIfExisting", "True" }, + { "octopusDefaultWorkerPoolIdOrName", workerPoolId }, + { "isDynamic", "True" } + }; + + return new ServiceMessage( + "create-azurewebapptarget", + parameters.Where(p => p.Value != null).ToDictionary(p => p.Key, p => p.Value!)); + } + + public static ServiceMessage CreateWebAppDeploymentSlotTargetCreationServiceMessage(string resourceGroupName, string webAppName, string slotName, string accountId, string role, string? workerPoolId) + { + var parameters = new Dictionary { + { "name", $"azure-web-app/{resourceGroupName}/{webAppName}/{slotName}" }, + { "azureWebApp", webAppName }, + { "azureResourceGroupName", resourceGroupName }, + { "azureWebAppSlot", slotName }, + { "octopusAccountIdOrName", accountId }, + { "octopusRoles", role }, + { "updateIfExisting", "True" }, + { "octopusDefaultWorkerPoolIdOrName", workerPoolId }, + { "isDynamic", "True" } + }; + + return new ServiceMessage( + "create-azurewebapptarget", + parameters.Where(p => p.Value != null).ToDictionary(p => p.Key, p => p.Value!)); + } + } } \ No newline at end of file diff --git a/source/Calamari/Calamari.csproj b/source/Calamari/Calamari.csproj index 05967836..410ff00c 100644 --- a/source/Calamari/Calamari.csproj +++ b/source/Calamari/Calamari.csproj @@ -28,6 +28,7 @@ all runtime; build; native; contentfiles; analyzers + diff --git a/source/Spike/Spike/Program.cs b/source/Spike/Spike/Program.cs deleted file mode 100644 index 8897d4fd..00000000 --- a/source/Spike/Spike/Program.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using System.Xml.Linq; -using Microsoft.Azure.Management.WebSites; -using Microsoft.Azure.Management.WebSites.Models; -using Microsoft.Identity.Client; -using Microsoft.IdentityModel.Clients.ActiveDirectory; -using Microsoft.Rest; - -namespace Spike -{ - class Program - { - static void Main(string[] args) - { - var userName = "$CMOcto"; //"0f91c747-93cf-464e-9a6c-d46c93eef239"; - var clientId = "0f91c747-93cf-464e-9a6c-d46c93eef239"; - - var pwd = "8BFXLK7s7xl0ArGqpcpbF4L7PjsdfCECyiGHtzuC7awdvNjG5MFYzQcM6hPF"; //"EU.M~6P3pCHe4K__x3~jif.keOtae5A7Xz"; - var clientSecret = "EU.M~6P3pCHe4K__x3~jif.keOtae5A7Xz"; - - var tenantId = "27312afb-009f-4fed-a8bb-9737425cc42a"; - - var appUrl = @"https://cmocto.scm.azurewebsites.net/api/zipdeploy"; - var zipPath = @"..\..\..\..\..\Calamari.Tests\Packages\HelloWorld.zip"; - - //var authResponse = GetAuthToken(clientId, tenantId, clientSecret).Result; - var token = GetAuthToken(tenantId, clientId, clientSecret); - - var creds = GetPublishProfileCreds(new TokenCredentials(token), "chrisplayground", - "cmocto"); - var credential = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{creds.UserName}:{creds.Pwd}")); - - - var webClient = new WebClient {Headers = {[HttpRequestHeader.Authorization] = $"Basic {credential}"}}; - webClient.Headers[HttpRequestHeader.ContentType] = "application/zip"; - byte[] response = null; - - try - { - response = webClient.UploadData(appUrl, File.ReadAllBytes(zipPath)); - } - catch (Exception ex) - { - if (response != null) - { - Console.WriteLine(response); - } - throw ex; - } - } - - #region [ ServicePrincipal.cs ] - - private static string GetAuthToken(string tenantId, string applicationId, string password) - { - var activeDirectoryEndPoint = @"https://login.windows.net/"; - var managementEndPoint = @"https://management.azure.com/"; - var authContext = GetContextUri(activeDirectoryEndPoint, tenantId); - //Log.Verbose($"Authentication Context: {authContext}"); - var context = new AuthenticationContext(authContext); - var result = context.AcquireTokenAsync(managementEndPoint, new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(applicationId, password)).Result; - return result.AccessToken; - } - - static string GetContextUri(string activeDirectoryEndPoint, string tenantId) - { - if (!activeDirectoryEndPoint.EndsWith("/")) - { - return $"{activeDirectoryEndPoint}/{tenantId}"; - } - return $"{activeDirectoryEndPoint}{tenantId}"; - } - - #endregion - - static PublishProfileCreds GetPublishProfileCreds(TokenCredentials tokenCredentials, string resourceGroupName, string siteName) - { - var resourceManagementEndpoint = "https://management.azure.com/"; - //var scopes = new[] { "user.read" }; - //var app = ConfidentialClientApplicationBuilder - - var webAppClient = new WebSiteManagementClient(new Uri(resourceManagementEndpoint), tokenCredentials) - {SubscriptionId = "84d2a09b-50bf-4ad3-98ec-4bc2bec36820"}; - - var options = new CsmPublishingProfileOptions {Format = "WebDeploy"}; - using var stream = webAppClient.WebApps - .ListPublishingProfileXmlWithSecretsAsync(resourceGroupName, siteName, options).Result; - - using var streamReader = new StreamReader(stream); - var text = streamReader.ReadToEnd(); - - var document = XDocument.Parse(text); - - var profile = (from el in document.Descendants("publishProfile") - where string.Compare(el.Attribute("publishMethod")?.Value, "MSDeploy", StringComparison.OrdinalIgnoreCase) == 0 - select new - { - PublishUrl = $"https://{el.Attribute("publishUrl")?.Value}", - Username = el.Attribute("userName")?.Value, - Password = el.Attribute("userPWD")?.Value, - Site = el.Attribute("msdeploySite")?.Value - }).FirstOrDefault(); - - if (profile == null) - { - throw new Exception("Failed to retrieve publishing profile."); - } - - return new PublishProfileCreds(profile.Username, profile.Password); - } - } -} diff --git a/source/Spike/Spike/PublishProfileCreds.cs b/source/Spike/Spike/PublishProfileCreds.cs deleted file mode 100644 index 30767d58..00000000 --- a/source/Spike/Spike/PublishProfileCreds.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Spike -{ - public class PublishProfileCreds - { - public string UserName { get; } - public string Pwd { get; } - - public PublishProfileCreds(string userName, string pwd) - { - UserName = userName; - Pwd = pwd; - } - } -} diff --git a/source/Spike/Spike/Spike.csproj b/source/Spike/Spike/Spike.csproj deleted file mode 100644 index 71f1b764..00000000 --- a/source/Spike/Spike/Spike.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - Exe - netcoreapp3.1 - - - - - - - - -