Skip to content

Commit

Permalink
feat: add support for configuring managed identities; resolves JetBra…
Browse files Browse the repository at this point in the history
  • Loading branch information
druttka committed Mar 28, 2023
1 parent 2a1c658 commit 3711b5f
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 5 deletions.
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

0 comments on commit 3711b5f

Please sign in to comment.