Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for configuring managed identities; #142 #147

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ class AzureCloudClientFactory(cloudRegistrar: CloudRegistrar,
param.getParameter(AzureConstants.ENABLE_SPOT_PRICE)?.toBoolean(),
param.getParameter(AzureConstants.SPOT_PRICE)?.toInt(),
param.getParameter(AzureConstants.ENABLE_ACCELERATED_NETWORKING)?.toBoolean(),
param.getParameter(AzureConstants.DISABLE_TEMPLATE_MODIFICATION)?.toBoolean())
param.getParameter(AzureConstants.DISABLE_TEMPLATE_MODIFICATION)?.toBoolean(),
param.getParameter(AzureConstants.USER_ASSIGNED_IDENTITY) ?: "",
param.getParameter(AzureConstants.ENABLE_SYSTEM_ASSIGNED_IDENTITY)?.toBoolean()
)
}.apply {
AzureUtils.setPasswords(AzureCloudImageDetails::class.java, params, this)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ class AzureCloudImageDetails(
@SerializedName(AzureConstants.ENABLE_ACCELERATED_NETWORKING)
val enableAcceleratedNetworking: Boolean?,
@SerializedName(AzureConstants.DISABLE_TEMPLATE_MODIFICATION)
val disableTemplateModification: Boolean?
val disableTemplateModification: Boolean?,
@SerializedName(AzureConstants.USER_ASSIGNED_IDENTITY)
val userAssignedIdentity: String? = null,
@SerializedName(AzureConstants.ENABLE_SYSTEM_ASSIGNED_IDENTITY)
val enableSystemAssignedIdentity: Boolean?

) : CloudImagePasswordDetails {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ class AzureConstants {
val disableTemplateModification: String
get() = DISABLE_TEMPLATE_MODIFICATION

val userAssignedIdentity: String
get() = USER_ASSIGNED_IDENTITY

val enableSystemAssignedIdentity: String
get() = ENABLE_SYSTEM_ASSIGNED_IDENTITY

companion object {
const val CLOUD_CODE = "arm"

Expand Down Expand Up @@ -189,6 +195,8 @@ class AzureConstants {
const val SPOT_PRICE = "spotPrice"
const val ENABLE_ACCELERATED_NETWORKING = "enableAcceleratedNetworking"
const val DISABLE_TEMPLATE_MODIFICATION = "disableTemplateModification"
const val USER_ASSIGNED_IDENTITY = "userAssignedIdentity"
const val ENABLE_SYSTEM_ASSIGNED_IDENTITY = "enableSystemAssignedIdentity"

const val TAG_SERVER = "teamcity-server"
const val TAG_PROFILE = "teamcity-profile"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ class AzureImageHandler(private val connector: AzureApiConnector) : AzureHandler
if (details.enableAcceleratedNetworking == true) {
builder.enableAcceleratedNerworking()
}

builder.setupIdentity(details.userAssignedIdentity, details.enableSystemAssignedIdentity)
builder
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,12 @@ class ArmTemplateBuilder(template: String, private val disableTemplateModificati
}
}

private fun getFirstResourceOfType(type: String): ObjectNode {
val resources = root["resources"] as ArrayNode
val groups = resources.filterIsInstance<ObjectNode>().first { it["type"].asText() == type }
return groups
}

private fun getPropertiesOfResource(resourceName: String): ObjectNode {
return getPropertiesOfResource("name", resourceName)
}
Expand Down Expand Up @@ -428,6 +434,26 @@ class ArmTemplateBuilder(template: String, private val disableTemplateModificati
return this
}

fun setupIdentity(userAssignedIdentity: String?, enableSystemAssignedIdentity: Boolean?): ArmTemplateBuilder {
val resource = getFirstResourceOfType("Microsoft.Compute/virtualMachines")

val hasUserAssigned = !userAssignedIdentity.isNullOrEmpty();
val hasSystemAssigned = enableSystemAssignedIdentity == true;

if (hasUserAssigned || hasSystemAssigned) {
val identity = resource.putObject("identity")

val fullType = if (hasUserAssigned && hasSystemAssigned) "SystemAssigned, UserAssigned" else if (hasSystemAssigned) "SystemAssigned" else "UserAssigned"
identity.put("type", fullType)

if (hasUserAssigned) {
identity.putObject("userAssignedIdentities").putObject(userAssignedIdentity)
}
}

return this
}

companion object {
private val LOG = Logger.getInstance(ArmTemplateBuilder::class.java.name)
private val PRICE_DIVIDER = 100000F
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ function ArmImagesViewModel($, ko, dialog, config) {
self.template = ko.observable('');
self.disableTemplateModification = ko.observable(false);

self.userAssignedIdentity = ko.observable();
self.enableSystemAssignedIdentity = ko.observable(false);

var requiredForDeployment = {
required: {
onlyIf: function () {
Expand Down Expand Up @@ -416,6 +419,8 @@ function ArmImagesViewModel($, ko, dialog, config) {
.extend({min: 0.00001, max: 20000}),
enableAcceleratedNetworking: ko.observable(false),
disableTemplateModification: self.disableTemplateModification,
userAssignedIdentity: ko.observable(),
enableSystemAssignedIdentity: self.enableSystemAssignedIdentity
});

// Data from Azure APIs
Expand Down Expand Up @@ -671,6 +676,8 @@ function ArmImagesViewModel($, ko, dialog, config) {
image.enableSpotPrice = JSON.parse(image.enableSpotPrice || "false");
image.enableAcceleratedNetworking = JSON.parse(image.enableAcceleratedNetworking || "false");
image.disableTemplateModification = JSON.parse(image.disableTemplateModification || "false");
image.userAssignedIdentity = image.userAssignedIdentity;
image.enableSystemAssignedIdentity = JSON.parse(image.enableSystemAssignedIdentity || "false");
});

self.images(images);
Expand Down Expand Up @@ -714,7 +721,9 @@ function ArmImagesViewModel($, ko, dialog, config) {
customTags: "",
spotVm: false,
enableSpotPrice: false,
spotPrice: spotPriceDefault * priceDivider
spotPrice: spotPriceDefault * priceDivider,
userAssignedIdentity: "",
systemAssignedIdentity: false,
};

// Pre-fill collections while loading resources
Expand Down Expand Up @@ -788,6 +797,8 @@ function ArmImagesViewModel($, ko, dialog, config) {
model.spotPrice(image.spotPrice != null ? image.spotPrice/priceDivider : undefined);
model.enableAcceleratedNetworking(image.enableAcceleratedNetworking);
model.disableTemplateModification(image.disableTemplateModification);
model.userAssignedIdentity(image.userAssignedIdentity);
model.enableSystemAssignedIdentity(image.enableSystemAssignedIdentity);

model.registryPassword("");
model.vmPassword("");
Expand Down Expand Up @@ -847,7 +858,9 @@ function ArmImagesViewModel($, ko, dialog, config) {
enableSpotPrice: model.enableSpotPrice(),
spotPrice: model.spotPrice() != null ? Math.trunc(parseFloat(model.spotPrice())*priceDivider) : undefined,
enableAcceleratedNetworking: model.enableAcceleratedNetworking(),
disableTemplateModification: model.disableTemplateModification()
disableTemplateModification: model.disableTemplateModification(),
userAssignedIdentity: model.userAssignedIdentity(),
enableSystemAssignedIdentity: model.enableSystemAssignedIdentity(),
};

var originalImage = self.originalImage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,27 @@
<span class="error option-error" data-bind="validationMessage: image().vmPassword"></span>
</td>
</tr>
<tr data-bind="if: image().imageType() != 'Template' && image().imageType() != 'Container'">
<th><label for="${cons.userAssignedIdentity}">User managed identity:</label></th>
<td>
<input type="text" id="${cons.userAssignedIdentity}" class="longField ignoreModified"
data-bind="textInput: image().userAssignedIdentity"/>
<span class="smallNote">Supply the ARM resource id for the identity according to the <a
href="https://learn.microsoft.com/en-us/azure/templates/microsoft.compute/virtualmachines?pivots=deployment-language-bicep#virtualmachineidentity"
target="_blank"
rel="noopener noreferrer">virtual machine identity schema.</a></span></span>
<span class="error option-error" data-bind="validationMessage: image().userAssignedIdentity"></span>
</td>
</tr>
<tr data-bind="if: image().imageType() != 'Template' && image().imageType() != 'Container'">
<th class="noBorder"><label for="${cons.enableSystemAssignedIdentity}">Use System Assigned Identity:</label></th>
<td>
<input type="checkbox" name="${cons.enableSystemAssignedIdentity}" data-bind="checked: image().enableSystemAssignedIdentity"/>
<span class="smallNote">
Create a system-assigned identity.
</span>
</td>
</tr>
<tr data-bind="if: image().imageType() == 'Template'">
<th class="noBorder"><label for="${cons.template}">ARM Template: <l:star/></label></th>
<td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,73 @@ class ArmTemplateBuilderTest {
Assert.assertEquals(builder.toString(),
"{\"resources\":[{\"type\":\"Microsoft.Network/networkInterfaces\",\"name\":\"myName\",\"properties\":{\"enableAcceleratedNetworking\":true}}]}")
}

fun testSetupSystemIdentity() {
val builder = ArmTemplateBuilder("""{"resources": [
{
"type": "Microsoft.Compute/virtualMachines",
"name": "myName",
"properties": {
}
}
]}""").setupIdentity(null, true)

Assert.assertEquals(builder.toString(),
"{\"resources\":[{\"type\":\"Microsoft.Compute/virtualMachines\",\"name\":\"myName\",\"properties\":{},\"identity\":{\"type\":\"SystemAssigned\"}}]}")
}

fun testSetupSystemAssignedIdentity() {
val builder = ArmTemplateBuilder("""{"resources": [
{
"type": "Microsoft.Compute/virtualMachines",
"name": "myName",
"properties": {
}
}
]}""").setupIdentity(null, true)

Assert.assertEquals(builder.toString(),
"{\"resources\":[{\"type\":\"Microsoft.Compute/virtualMachines\",\"name\":\"myName\",\"properties\":{},\"identity\":{\"type\":\"SystemAssigned\"}}]}")
}

fun testSetupUserAssignedIdentity() {
val builder = ArmTemplateBuilder("""{"resources": [
{
"type": "Microsoft.Compute/virtualMachines",
"name": "myName",
"properties": {
}
}
]}""").setupIdentity("someIdentity", false)

Assert.assertEquals(builder.toString(),
"{\"resources\":[{\"type\":\"Microsoft.Compute/virtualMachines\",\"name\":\"myName\",\"properties\":{},\"identity\":{\"type\":\"UserAssigned\",\"userAssignedIdentities\":{\"someIdentity\":{}}}}]}") }

fun testSetupSystemAndUserAssignedIdentity() {
val builder = ArmTemplateBuilder("""{"resources": [
{
"type": "Microsoft.Compute/virtualMachines",
"name": "myName",
"properties": {
}
}
]}""").setupIdentity("someIdentity", true)

Assert.assertEquals(builder.toString(),
"{\"resources\":[{\"type\":\"Microsoft.Compute/virtualMachines\",\"name\":\"myName\",\"properties\":{},\"identity\":{\"type\":\"SystemAssigned, UserAssigned\",\"userAssignedIdentities\":{\"someIdentity\":{}}}}]}")
}

fun testSetupIdentityWithNoIdentities() {
val builder = ArmTemplateBuilder("""{"resources": [
{
"type": "Microsoft.Compute/virtualMachines",
"name": "myName",
"properties": {
}
}
]}""").setupIdentity("", false)

Assert.assertEquals(builder.toString(),
"{\"resources\":[{\"type\":\"Microsoft.Compute/virtualMachines\",\"name\":\"myName\",\"properties\":{}}]}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ class AzureCloudImageTest : MockObjectTestCase() {
enableSpotPrice = null,
spotPrice = null,
enableAcceleratedNetworking = null,
disableTemplateModification = null
disableTemplateModification = null,
userAssignedIdentity = null,
enableSystemAssignedIdentity = null
)

myJob = SupervisorJob()
Expand Down