diff --git a/.gitignore b/.gitignore index 5e1422c..de6b2db 100644 --- a/.gitignore +++ b/.gitignore @@ -10,24 +10,10 @@ /test/version_tmp/ /tmp/ -# Used by dotenv library to load environment variables. -# .env - ## Specific to RubyMotion: .dat* .repl_history build/ -*.bridgesupport -build-iPhoneOS/ -build-iPhoneSimulator/ - -## Specific to RubyMotion (use of CocoaPods): -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# -# vendor/Pods/ ## Documentation cache and generated files: /.yardoc/ @@ -48,3 +34,7 @@ build-iPhoneSimulator/ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc + +/.idea/ +.env + diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..83e16f8 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..d1b39c3 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +ruby@compute-ruby-msi-vm diff --git a/CHANGELOG.md b/CHANGELOG.md index 9824752..7e36d76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,5 @@ -## [project-title] Changelog - - -# x.y.z (yyyy-mm-dd) - -*Features* -* ... - -*Bug Fixes* -* ... - -*Breaking Changes* -* ... + ## 2017.09.12 + + *Features* + * Adding initial sample to create Managed Service Identity Azure virtual machine. + \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 4bd3eba..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributing to [project-title] - -This project welcomes contributions and suggestions. Most contributions require you to agree to a -Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us -the rights to use your contribution. For details, visit https://cla.microsoft.com. - -When you submit a pull request, a CLA-bot will automatically determine whether you need to provide -a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - - - [Code of Conduct](#coc) - - [Issues and Bugs](#issue) - - [Feature Requests](#feature) - - [Submission Guidelines](#submit) - -## Code of Conduct -Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). - -## Found an Issue? -If you find a bug in the source code or a mistake in the documentation, you can help us by -[submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can -[submit a Pull Request](#submit-pr) with a fix. - -## Want a Feature? -You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub -Repository. If you would like to *implement* a new feature, please submit an issue with -a proposal for your work first, to be sure that we can use it. - -* **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). - -## Submission Guidelines - -### Submitting an Issue -Before you submit an issue, search the archive, maybe your question was already answered. - -If your issue appears to be a bug, and hasn't been reported, open a new issue. -Help us to maximize the effort we can spend fixing issues and adding new -features, by not reporting duplicate issues. Providing the following information will increase the -chances of your issue being dealt with quickly: - -* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps -* **Version** - what version is affected (e.g. 0.1.2) -* **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you -* **Browsers and Operating System** - is this a problem with all browsers? -* **Reproduce the Error** - provide a live example or a unambiguous set of steps -* **Related Issues** - has a similar issue been reported before? -* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be - causing the problem (line of code or commit) - -You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. - -### Submitting a Pull Request (PR) -Before you submit your Pull Request (PR) consider the following guidelines: - -* Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR - that relates to your submission. You don't want to duplicate effort. - -* Make your changes in a new git fork: - -* Commit your changes using a descriptive commit message -* Push your fork to GitHub: -* In GitHub, create a pull request -* If we suggest changes then: - * Make the required updates. - * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): - - ```shell - git rebase master -i - git push -f - ``` - -That's it! Thank you for your contribution! diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..1fc1bb0 --- /dev/null +++ b/Gemfile @@ -0,0 +1,21 @@ +# encoding: utf-8 +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. + +source 'https://rubygems.org' + +group :development, :test do + gem 'rake', '~>11.1' + gem 'rspec', '~>3.4' + gem 'dotenv', '~>2.1' + gem 'vcr', '~>3.0' + gem 'webmock', '~>2.0' + gem 'climate_control' +end + +gem 'azure_mgmt_authorization', '~>0.12.0' +gem 'azure_mgmt_resources', '~>0.12.0' +gem 'azure_mgmt_compute', '~>0.12.0' +gem 'azure_mgmt_network', '~>0.12.0' +gem 'azure_mgmt_storage', '~>0.12.0' +gem 'haikunator', '~>1.1' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..a25ed63 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,87 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.5.1) + public_suffix (~> 2.0, >= 2.0.2) + azure_mgmt_authorization (0.12.0) + ms_rest_azure (~> 0.9.0) + azure_mgmt_compute (0.12.0) + ms_rest_azure (~> 0.9.0) + azure_mgmt_network (0.12.0) + ms_rest_azure (~> 0.9.0) + azure_mgmt_resources (0.12.0) + ms_rest_azure (~> 0.9.0) + azure_mgmt_storage (0.12.0) + ms_rest_azure (~> 0.9.0) + climate_control (0.2.0) + concurrent-ruby (1.0.5) + crack (0.4.3) + safe_yaml (~> 1.0.0) + diff-lcs (1.3) + domain_name (0.5.20170404) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.2.1) + faraday (0.13.1) + multipart-post (>= 1.2, < 3) + faraday-cookie_jar (0.0.6) + faraday (>= 0.7.4) + http-cookie (~> 1.0.0) + haikunator (1.1.0) + hashdiff (0.3.5) + http-cookie (1.0.3) + domain_name (~> 0.5) + ms_rest (0.7.1) + concurrent-ruby (~> 1.0) + faraday (~> 0.9) + timeliness (~> 0.3) + ms_rest_azure (0.9.0) + concurrent-ruby (~> 1.0) + faraday (~> 0.9) + faraday-cookie_jar (~> 0.0.6) + ms_rest (~> 0.7.0) + multipart-post (2.0.0) + public_suffix (2.0.5) + rake (11.3.0) + rspec (3.6.0) + rspec-core (~> 3.6.0) + rspec-expectations (~> 3.6.0) + rspec-mocks (~> 3.6.0) + rspec-core (3.6.0) + rspec-support (~> 3.6.0) + rspec-expectations (3.6.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.6.0) + rspec-mocks (3.6.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.6.0) + rspec-support (3.6.0) + safe_yaml (1.0.4) + timeliness (0.3.8) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.4) + vcr (3.0.3) + webmock (2.3.2) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff + +PLATFORMS + ruby + +DEPENDENCIES + azure_mgmt_authorization (~> 0.12.0) + azure_mgmt_compute (~> 0.12.0) + azure_mgmt_network (~> 0.12.0) + azure_mgmt_resources (~> 0.12.0) + azure_mgmt_storage (~> 0.12.0) + climate_control + dotenv (~> 2.1) + haikunator (~> 1.1) + rake (~> 11.1) + rspec (~> 3.4) + vcr (~> 3.0) + webmock (~> 2.0) + +BUNDLED WITH + 1.14.3 diff --git a/README.md b/README.md index 5efee44..80ee816 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,337 @@ -# Project Name +--- +services: compute +platforms: ruby +author: vishrutshah +--- -(short, 1-3 sentenced, description of the project) +# Create Azure virtual machines with Managed Service Identity using Ruby +This sample demonstrates how to create your Azure virtual machines with Managed Service Identity using the Ruby SDK. -## Features +**On this page** -This project framework provides the following features: +- [Run this sample](#run) +- [What is example.rb doing?](#example) + - [Create a virtual network](#vnet) + - [Create a public IP address](#ipaddress) + - [Create a network interface](#nic) + - [Create a virtual machine with system identity](#vm) + - [Add MSI extension to the VM](#extension) + - [Create role assignment for the VM](#role-assignment) + - [Verify MSI extension is running on VM by logging-in via ssh](#msi-extension) + - [Delete the resources](#delete) -* Feature 1 -* Feature 2 -* ... -## Getting Started + +## Run this sample -### Prerequisites +1. If you don't already have it, [install Ruby and the Ruby DevKit](https://www.ruby-lang.org/en/documentation/installation/). -(ideally very short, if any) +2. If you don't have bundler, install it. -- OS -- Library version -- ... + ``` + gem install bundler + ``` + +3. Clone the repository. -### Installation + ``` + git clone https://github.com/Azure-Samples/compute-ruby-msi-vm.git + ``` -(ideally very short) +4. Install the dependencies using bundle. -- npm install [package name] -- mvn install -- ... + ``` + cd compute-ruby-msi-vm + bundle install + ``` + +5. Create an Azure service principal either through -### Quickstart -(Add steps to get up and running quickly) + [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?toc=%2Fazure%2Fazure-resource-manager%2Ftoc.json&view=azure-cli-latest), + [PowerShell](https://azure.microsoft.com/documentation/articles/resource-group-authenticate-service-principal/) + or [the portal](https://azure.microsoft.com/documentation/articles/resource-group-create-service-principal-portal/). -1. git clone [repository clone url] -2. cd [respository name] -3. ... + :exclamation: NOTE :exclamation: Please make sure to create an role assignment with `Owner` role. In case of insufficient permissions, role + assignment may fail for this sample. +6. Set the following environment variables using the information from the service principle that you created. -## Demo + ``` + export AZURE_TENANT_ID={your tenant id} + export AZURE_CLIENT_ID={your client id} + export AZURE_CLIENT_SECRET={your client secret} + export AZURE_SUBSCRIPTION_ID={your subscription id} + ``` -A demo app is included to show how to use the project. + > [AZURE.NOTE] On Windows, use `set` instead of `export`. -To run the demo, follow these steps: +7. Run the sample. -(Add steps to start up the demo) + ``` + bundle exec ruby example.rb + ``` -1. -2. -3. + +## What does example.rb doing? -## Resources +This sample starts by setting up ResourceManagementClient, the resource provider clients, a resource group, and a storage account using your subscription and credentials. -(Any additional resources or related projects) +```ruby +subscription_id = ENV['AZURE_SUBSCRIPTION_ID'] || '11111111-1111-1111-1111-111111111111' # your Azure Subscription Id + provider = MsRestAzure::ApplicationTokenProvider.new( + ENV['AZURE_TENANT_ID'], + ENV['AZURE_CLIENT_ID'], + ENV['AZURE_CLIENT_SECRET']) + credentials = MsRest::TokenCredentials.new(provider) + resource_client = Azure::ARM::Resources::ResourceManagementClient.new(credentials) + resource_client.subscription_id = subscription_id + network_client = Azure::ARM::Network::NetworkManagementClient.new(credentials) + network_client.subscription_id = subscription_id + storage_client = Azure::ARM::Storage::StorageManagementClient.new(credentials) + storage_client.subscription_id = subscription_id + compute_client = Azure::ARM::Compute::ComputeManagementClient.new(credentials) + compute_client.subscription_id = subscription_id -- Link to supporting information -- Link to similar sample -- ... + # + # Managing resource groups + # + resource_group_params = Azure::ARM::Resources::Models::ResourceGroup.new.tap do |rg| + rg.location = WEST_US + end + + # Create Resource group + puts 'Create Resource Group' + print_group resource_client.resource_groups.create_or_update(GROUP_NAME, resource_group_params) + + postfix = rand(1000) + storage_account_name = "rubystor#{postfix}" + puts "Creating a premium storage account with encryption off named #{storage_account_name} in resource group #{GROUP_NAME}" + storage_create_params = StorageModels::StorageAccountCreateParameters.new.tap do |account| + account.location = WEST_US + account.sku = StorageModels::Sku.new.tap do |sku| + sku.name = StorageModels::SkuName::PremiumLRS + sku.tier = StorageModels::SkuTier::Premium + end + account.kind = StorageModels::Kind::Storage + account.encryption = StorageModels::Encryption.new.tap do |encrypt| + encrypt.services = StorageModels::EncryptionServices.new.tap do |services| + services.blob = StorageModels::EncryptionService.new.tap do |service| + service.enabled = false + end + end + end + end + print_item storage_account = storage_client.storage_accounts.create(GROUP_NAME, storage_account_name, storage_create_params) +``` + + +### Create a virtual network +Now, we will create a virtual network and configure subnet for the virtual machine. + +```ruby +puts 'Creating a virtual network for the VM' +vnet_create_params = NetworkModels::VirtualNetwork.new.tap do |vnet| + vnet.location = WEST_US + vnet.address_space = NetworkModels::AddressSpace.new.tap do |addr_space| + addr_space.address_prefixes = ['10.0.0.0/16'] + end + vnet.dhcp_options = NetworkModels::DhcpOptions.new.tap do |dhcp| + dhcp.dns_servers = ['8.8.8.8'] + end + vnet.subnets = [ + NetworkModels::Subnet.new.tap do |subnet| + subnet.name = 'rubySampleSubnet' + subnet.address_prefix = '10.0.0.0/24' + end + ] +end +print_item vnet = network_client.virtual_networks.create_or_update(GROUP_NAME, 'sample-ruby-vnet', vnet_create_params) +``` + + +### Create a public IP address +Now, we will create a public IP address using dynamic IP allocation method for the Azure VM. + +```ruby +puts 'Creating a public IP address for the VM' +public_ip_params = NetworkModels::PublicIPAddress.new.tap do |ip| + ip.location = WEST_US + ip.public_ipallocation_method = NetworkModels::IPAllocationMethod::Dynamic + ip.dns_settings = NetworkModels::PublicIPAddressDnsSettings.new.tap do |dns| + dns.domain_name_label = 'msi-vm-domain-name-label' + end +end +print_item public_ip = network_client.public_ipaddresses.create_or_update(GROUP_NAME, 'sample-ruby-pubip', public_ip_params) +``` + + +### Create a network interface +Now, we will create a network interface and assign the public ip address created in previous step. + +```ruby +print_item nic = network_client.network_interfaces.create_or_update( + GROUP_NAME, + "sample-ruby-nic-#{vm_name}", + NetworkModels::NetworkInterface.new.tap do |interface| + interface.location = WEST_US + interface.ip_configurations = [ + NetworkModels::NetworkInterfaceIPConfiguration.new.tap do |nic_conf| + nic_conf.name = "sample-ruby-nic-#{vm_name}" + nic_conf.private_ipallocation_method = NetworkModels::IPAllocationMethod::Dynamic + nic_conf.subnet = subnet + nic_conf.public_ipaddress = public_ip + end + ] + end +) +``` + + +### Create a virtual machine with system identity +Now, we will set virtual machine parameters like `OSProfile`, `StorageProfile`, `OSDisk`, `HardwareProfile` & `NetworkProfile` as usual. We will +also set the `VirtualMachineIdentity` to be `SystemAssigned` specifically for creating managed service identity VM and then create the virtual machine. + +```ruby +puts 'Creating a Ubuntu 16.04.0-LTS Standard DS2 V2 virtual machine w/ a public IP' +vm_create_params = ComputeModels::VirtualMachine.new.tap do |vm| + vm.location = location + vm.os_profile = ComputeModels::OSProfile.new.tap do |os_profile| + os_profile.computer_name = vm_name + os_profile.admin_username = 'notAdmin' + os_profile.admin_password = 'Pa$$w0rd92' + end + + vm.storage_profile = ComputeModels::StorageProfile.new.tap do |store_profile| + store_profile.image_reference = ComputeModels::ImageReference.new.tap do |ref| + ref.publisher = 'canonical' + ref.offer = 'UbuntuServer' + ref.sku = '16.04.0-LTS' + ref.version = 'latest' + end + store_profile.os_disk = ComputeModels::OSDisk.new.tap do |os_disk| + os_disk.name = "sample-os-disk-#{vm_name}" + os_disk.caching = ComputeModels::CachingTypes::None + os_disk.create_option = ComputeModels::DiskCreateOptionTypes::FromImage + os_disk.vhd = ComputeModels::VirtualHardDisk.new.tap do |vhd| + vhd.uri = "https://#{storage_acct.name}.blob.core.windows.net/rubycontainer/#{vm_name}.vhd" + end + end + end + + vm.hardware_profile = ComputeModels::HardwareProfile.new.tap do |hardware| + hardware.vm_size = ComputeModels::VirtualMachineSizeTypes::StandardDS2V2 + end + + vm.network_profile = ComputeModels::NetworkProfile.new.tap do |net_profile| + net_profile.network_interfaces = [ + ComputeModels::NetworkInterfaceReference.new.tap do |ref| + ref.id = nic.id + ref.primary = true + end + ] + end + + # Use System Assigned Identity for the VM + vm.identity = ComputeModels::VirtualMachineIdentity.new.tap do |identity| + identity.type = ComputeModels::ResourceIdentityType::SystemAssigned + end +end + +ssh_pub_location = File.expand_path('~/.ssh/id_rsa.pub') +if File.exists? ssh_pub_location + puts "Found SSH public key in #{ssh_pub_location}. Disabling password and enabling SSH authentication." + key_data = File.read(ssh_pub_location) + puts "Using public key: #{key_data}" + vm_create_params.os_profile.linux_configuration = ComputeModels::LinuxConfiguration.new.tap do |linux| + linux.disable_password_authentication = true + linux.ssh = ComputeModels::SshConfiguration.new.tap do |ssh_config| + ssh_config.public_keys = [ + ComputeModels::SshPublicKey.new.tap do |pub_key| + pub_key.key_data = key_data + pub_key.path = '/home/notAdmin/.ssh/authorized_keys' + end + ] + end + end +end + +print_item vm = compute_client.virtual_machines.create_or_update(GROUP_NAME, "sample-ruby-vm-#{vm_name}", vm_create_params) +``` + + +### Add MSI extension to the VM +Now, we will add an VM extension `ManagedIdentityExtensionForLinux` for Azure VM and configure it to run on port `50342`. + +```ruby +puts "Install Managed Service Identity Extension" +ext_name = 'msiextension' +vm_extension = ComputeModels::VirtualMachineExtension.new.tap do |extension| + extension.publisher = 'Microsoft.ManagedIdentity' + extension.virtual_machine_extension_type = 'ManagedIdentityExtensionForLinux' + extension.type_handler_version = '1.0' + extension.auto_upgrade_minor_version = true + extension.settings = Hash.new.tap do |settings| + settings['port'] = '50342' + end + extension.location = WEST_US +end + +vm_ext = compute_client.virtual_machine_extensions.create_or_update(GROUP_NAME, "sample-ruby-vm-#{vm_name}", ext_name, vm_extension) +``` + + +### Create role assignment for the VM +Now, we will retrieve the default rbac role named as `Contributor`. To know more about the +default roles please visit [built-in-roles](https://docs.microsoft.com/en-us/azure/active-directory/role-based-access-built-in-roles). + +```ruby +puts "Getting the Role ID of Contributor of a Resource group: #{GROUP_NAME}" +role_name = 'Contributor' +roles = authorization_client.role_definitions.list(resource_group.id, "roleName eq '#{role_name}'") +contributor_role = roles.first +``` + +Now, we will assign `Contributor` role at the resource group level to allow managing Azure resources inside +this resource group. + +```ruby +puts 'Creating the role assignment for the VM' +role_assignment_params = AuthorizationModels::RoleAssignmentCreateParameters.new.tap do |role_param| + role_param.properties = AuthorizationModels::RoleAssignmentProperties.new.tap do |property| + property.principal_id = vm.identity.principal_id + property.role_definition_id = contributor_role.id + end +end +authorization_client.role_assignments.create(resource_group.id, SecureRandom.uuid, role_assignment_params) +``` + + +### Verify MSI extension is running on VM by logging-in via ssh +Once the Azure VM has been created, we will verify that MSI extension is running on this VM. Managed Service Identity extension will run on +`localhost` and configured port, here `50342`. Follow example [here](https://github.com/Azure/azure-sdk-for-ruby/blob/master/runtime/ms_rest_azure/README.md#utilizing-msimanaged-service-identity-token-provider) to +find out the usage. + +``` +ssh -p 22 notAdmin@msi-vm-domain-name-label.westus.cloudapp.azure.com +``` +``` +notAdmin@msi-vm:~$ netstat -tlnp +(Not all processes could be identified, non-owned process info + will not be shown, you would have to be root to see it all.) +Active Internet connections (only servers) +Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name +tcp 0 0 127.0.0.1:50342 0.0.0.0:* LISTEN - +... + +``` +``` +exit +``` + + +### Delete the resources +Now, we will delete all the resources created using this example. Please comment this out to keep the resources alive in you Azure subscription. + +```ruby +resource_client.resource_groups.delete(GROUP_NAME) +``` \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..822d746 --- /dev/null +++ b/Rakefile @@ -0,0 +1,4 @@ +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) +task :default => :spec diff --git a/example.rb b/example.rb new file mode 100644 index 0000000..2c99cd7 --- /dev/null +++ b/example.rb @@ -0,0 +1,279 @@ +#!/usr/bin/env ruby + +require 'azure_mgmt_resources' +require 'azure_mgmt_network' +require 'azure_mgmt_storage' +require 'azure_mgmt_compute' +require 'azure_mgmt_authorization' + +WEST_US = 'westus' +GROUP_NAME = 'azure-sample-compute-msi' + +StorageModels = Azure::ARM::Storage::Models +NetworkModels = Azure::ARM::Network::Models +ComputeModels = Azure::ARM::Compute::Models +ResourceModels = Azure::ARM::Resources::Models +AuthorizationModels = Azure::ARM::Authorization::Models + +# This sample shows how to create a Azure virtual machines with Managed Service Identity using the Azure Resource Manager APIs for Ruby. +# +# This script expects that the following environment vars are set: +# +# AZURE_TENANT_ID: with your Azure Active Directory tenant id or domain +# AZURE_CLIENT_ID: with your Azure Active Directory Application Client ID +# AZURE_CLIENT_SECRET: with your Azure Active Directory Application Secret +# AZURE_SUBSCRIPTION_ID: with your Azure Subscription Id +# +def run_example + # + # Create the Resource Manager Client with an Application (service principal) token provider + # + subscription_id = ENV['AZURE_SUBSCRIPTION_ID'] || '11111111-1111-1111-1111-111111111111' # your Azure Subscription Id + provider = MsRestAzure::ApplicationTokenProvider.new( + ENV['AZURE_TENANT_ID'], + ENV['AZURE_CLIENT_ID'], + ENV['AZURE_CLIENT_SECRET']) + credentials = MsRest::TokenCredentials.new(provider) + resource_client = Azure::ARM::Resources::ResourceManagementClient.new(credentials) + resource_client.subscription_id = subscription_id + network_client = Azure::ARM::Network::NetworkManagementClient.new(credentials) + network_client.subscription_id = subscription_id + storage_client = Azure::ARM::Storage::StorageManagementClient.new(credentials) + storage_client.subscription_id = subscription_id + compute_client = Azure::ARM::Compute::ComputeManagementClient.new(credentials) + compute_client.subscription_id = subscription_id + authorization_client = Azure::ARM::Authorization::AuthorizationManagementClient.new(credentials) + authorization_client.subscription_id = subscription_id + + # + # Managing resource groups + # + resource_group_params = Azure::ARM::Resources::Models::ResourceGroup.new.tap do |rg| + rg.location = WEST_US + end + + # Create Resource group + puts 'Create Resource Group' + print_group resource_group = resource_client.resource_groups.create_or_update(GROUP_NAME, resource_group_params) + + postfix = rand(1000) + storage_account_name = "rubystor#{postfix}" + puts "Creating a premium storage account with encryption off named #{storage_account_name} in resource group #{GROUP_NAME}" + storage_create_params = StorageModels::StorageAccountCreateParameters.new.tap do |account| + account.location = WEST_US + account.sku = StorageModels::Sku.new.tap do |sku| + sku.name = StorageModels::SkuName::PremiumLRS + sku.tier = StorageModels::SkuTier::Premium + end + account.kind = StorageModels::Kind::Storage + account.encryption = StorageModels::Encryption.new.tap do |encrypt| + encrypt.services = StorageModels::EncryptionServices.new.tap do |services| + services.blob = StorageModels::EncryptionService.new.tap do |service| + service.enabled = false + end + end + end + end + print_item storage_account = storage_client.storage_accounts.create(GROUP_NAME, storage_account_name, storage_create_params) + + puts 'Creating a virtual network for the VM' + vnet_create_params = NetworkModels::VirtualNetwork.new.tap do |vnet| + vnet.location = WEST_US + vnet.address_space = NetworkModels::AddressSpace.new.tap do |addr_space| + addr_space.address_prefixes = ['10.0.0.0/16'] + end + vnet.dhcp_options = NetworkModels::DhcpOptions.new.tap do |dhcp| + dhcp.dns_servers = ['8.8.8.8'] + end + vnet.subnets = [ + NetworkModels::Subnet.new.tap do |subnet| + subnet.name = 'rubySampleSubnet' + subnet.address_prefix = '10.0.0.0/24' + end + ] + end + print_item vnet = network_client.virtual_networks.create_or_update(GROUP_NAME, 'sample-ruby-vnet', vnet_create_params) + + puts 'Creating a public IP address for the VM' + public_ip_params = NetworkModels::PublicIPAddress.new.tap do |ip| + ip.location = WEST_US + ip.public_ipallocation_method = NetworkModels::IPAllocationMethod::Dynamic + ip.dns_settings = NetworkModels::PublicIPAddressDnsSettings.new.tap do |dns| + dns.domain_name_label = 'msi-vm-domain-name-label' + end + end + print_item public_ip = network_client.public_ipaddresses.create_or_update(GROUP_NAME, 'sample-ruby-pubip', public_ip_params) + + vm = create_vm(compute_client, network_client, WEST_US, 'msi-vm', storage_account, vnet.subnets[0], public_ip) + + puts "Getting the Role ID of Contributor of a Resource group: #{GROUP_NAME}" + role_name = 'Contributor' + roles = authorization_client.role_definitions.list(resource_group.id, "roleName eq '#{role_name}'") + contributor_role = roles.first + puts contributor_role + + puts 'Creating the role assignment for the VM' + role_assignment_params = AuthorizationModels::RoleAssignmentCreateParameters.new.tap do |role_param| + role_param.properties = AuthorizationModels::RoleAssignmentProperties.new.tap do |property| + property.principal_id = vm.identity.principal_id + property.role_definition_id = contributor_role.id + end + end + authorization_client.role_assignments.create(resource_group.id, SecureRandom.uuid, role_assignment_params) + + puts 'Listing all of the resources within the group' + resource_client.resources.list_by_resource_group(GROUP_NAME).each do |res| + print_item res + end + puts '' + + export_template(resource_client) + + puts "Thank you for creating managed service identity Azure VM." + puts "Use `netstat -tlnp` command verify that MSI service is running at 127.0.0.1:50342 address." + puts "Connect to your new virtual machine via: 'ssh -p 22 #{vm.os_profile.admin_username}@#{public_ip.dns_settings.fqdn}'. Admin Password is: Pa$$w0rd92" + + puts 'Press any key to continue and delete the sample resources' + gets + + # Delete Resource group and everything in it + puts 'Delete Resource Group' + resource_client.resource_groups.delete(GROUP_NAME) + puts "\nDeleted: #{GROUP_NAME}" + +end + +def print_group(resource) + puts "\tname: #{resource.name}" + puts "\tid: #{resource.id}" + puts "\tlocation: #{resource.location}" + puts "\ttags: #{resource.tags}" + puts "\tproperties:" + print_item(resource.properties) +end + +def print_item(resource) + resource.instance_variables.sort.each do |ivar| + str = ivar.to_s.gsub /^@/, '' + if resource.respond_to? str.to_sym + puts "\t\t#{str}: #{resource.send(str.to_sym)}" + end + end + puts "\n\n" +end + +def export_template(resource_client) + puts "Exporting the resource group template for #{GROUP_NAME}" + export_result = resource_client.resource_groups.export_template( + GROUP_NAME, + ResourceModels::ExportTemplateRequest.new.tap{ |req| req.resources = ['*'] } + ) + puts export_result.template + puts '' +end + +# Create a Virtual Machine, Install MSI extension and return it +def create_vm(compute_client, network_client, location, vm_name, storage_acct, subnet, public_ip) + puts "Creating a network interface for the VM #{vm_name}" + print_item nic = network_client.network_interfaces.create_or_update( + GROUP_NAME, + "sample-ruby-nic-#{vm_name}", + NetworkModels::NetworkInterface.new.tap do |interface| + interface.location = WEST_US + interface.ip_configurations = [ + NetworkModels::NetworkInterfaceIPConfiguration.new.tap do |nic_conf| + nic_conf.name = "sample-ruby-nic-#{vm_name}" + nic_conf.private_ipallocation_method = NetworkModels::IPAllocationMethod::Dynamic + nic_conf.subnet = subnet + nic_conf.public_ipaddress = public_ip + end + ] + end + ) + + puts 'Creating a Ubuntu 16.04.0-LTS Standard DS2 V2 virtual machine w/ a public IP' + vm_create_params = ComputeModels::VirtualMachine.new.tap do |vm| + vm.location = location + vm.os_profile = ComputeModels::OSProfile.new.tap do |os_profile| + os_profile.computer_name = vm_name + os_profile.admin_username = 'notAdmin' + os_profile.admin_password = 'Pa$$w0rd92' + end + + vm.storage_profile = ComputeModels::StorageProfile.new.tap do |store_profile| + store_profile.image_reference = ComputeModels::ImageReference.new.tap do |ref| + ref.publisher = 'canonical' + ref.offer = 'UbuntuServer' + ref.sku = '16.04.0-LTS' + ref.version = 'latest' + end + store_profile.os_disk = ComputeModels::OSDisk.new.tap do |os_disk| + os_disk.name = "sample-os-disk-#{vm_name}" + os_disk.caching = ComputeModels::CachingTypes::None + os_disk.create_option = ComputeModels::DiskCreateOptionTypes::FromImage + os_disk.vhd = ComputeModels::VirtualHardDisk.new.tap do |vhd| + vhd.uri = "https://#{storage_acct.name}.blob.core.windows.net/rubycontainer/#{vm_name}.vhd" + end + end + end + + vm.hardware_profile = ComputeModels::HardwareProfile.new.tap do |hardware| + hardware.vm_size = ComputeModels::VirtualMachineSizeTypes::StandardDS2V2 + end + + vm.network_profile = ComputeModels::NetworkProfile.new.tap do |net_profile| + net_profile.network_interfaces = [ + ComputeModels::NetworkInterfaceReference.new.tap do |ref| + ref.id = nic.id + ref.primary = true + end + ] + end + + # Use System Assigned Identity for the VM + vm.identity = ComputeModels::VirtualMachineIdentity.new.tap do |identity| + identity.type = ComputeModels::ResourceIdentityType::SystemAssigned + end + end + + ssh_pub_location = File.expand_path('~/.ssh/id_rsa.pub') + if File.exists? ssh_pub_location + puts "Found SSH public key in #{ssh_pub_location}. Disabling password and enabling SSH authentication." + key_data = File.read(ssh_pub_location) + puts "Using public key: #{key_data}" + vm_create_params.os_profile.linux_configuration = ComputeModels::LinuxConfiguration.new.tap do |linux| + linux.disable_password_authentication = true + linux.ssh = ComputeModels::SshConfiguration.new.tap do |ssh_config| + ssh_config.public_keys = [ + ComputeModels::SshPublicKey.new.tap do |pub_key| + pub_key.key_data = key_data + pub_key.path = '/home/notAdmin/.ssh/authorized_keys' + end + ] + end + end + end + + print_item vm = compute_client.virtual_machines.create_or_update(GROUP_NAME, "sample-ruby-vm-#{vm_name}", vm_create_params) + + puts "Install Managed Service Identity Extension" + ext_name = 'msiextension' + vm_extension = ComputeModels::VirtualMachineExtension.new.tap do |extension| + extension.publisher = 'Microsoft.ManagedIdentity' + extension.virtual_machine_extension_type = 'ManagedIdentityExtensionForLinux' + extension.type_handler_version = '1.0' + extension.auto_upgrade_minor_version = true + extension.settings = Hash.new.tap do |settings| + settings['port'] = '50342' + end + extension.location = WEST_US + end + + compute_client.virtual_machine_extensions.create_or_update(GROUP_NAME, "sample-ruby-vm-#{vm_name}", ext_name, vm_extension) + + compute_client.virtual_machines.get(GROUP_NAME, "sample-ruby-vm-#{vm_name}") +end + +if $0 == __FILE__ + run_example +end