From 59e49b3c6d3aca2cc69eb074861b169c6eae7006 Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Mon, 21 Aug 2017 12:56:53 -0700 Subject: [PATCH 1/6] Create Azure VM with Managed Service Identity --- .gitignore | 18 +--- .rspec | 2 + .ruby-version | 1 + .travis.yml | 10 ++ Gemfile | 21 ++++ Gemfile.lock | 89 +++++++++++++++++ README.md | 271 +++++++++++++++++++++++++++++++++++++++++++------- Rakefile | 4 + example.rb | 258 +++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 625 insertions(+), 49 deletions(-) create mode 100644 .rspec create mode 100644 .ruby-version create mode 100644 .travis.yml create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 Rakefile create mode 100644 example.rb 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/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4ad3bf5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: ruby +sudo: false + +cache: + bundler: true + +rvm: + - 2.2.4 + - 2.3.0 + - ruby-head diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..1707dee --- /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 'ms_rest_azure', path: '/Users/vishrut/git-repos/azure-sdk-for-ruby/runtime/ms_rest_azure' +gem 'azure_mgmt_resources', '~>0.11.0' +gem 'azure_mgmt_compute', '~>0.11.0' +gem 'azure_mgmt_network', '~>0.11.0' +gem 'azure_mgmt_storage', '~>0.11.0' +gem 'haikunator', '~>1.1' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..4a8a64a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,89 @@ +PATH + remote: /Users/vishrut/git-repos/azure-sdk-for-ruby/runtime/ms_rest_azure + specs: + ms_rest_azure (0.8.1) + concurrent-ruby (~> 1.0) + faraday (~> 0.9) + faraday-cookie_jar (~> 0.0.6) + ms_rest (~> 0.7.0) + +GEM + remote: https://rubygems.org/ + specs: + addressable (2.5.1) + public_suffix (~> 2.0, >= 2.0.2) + azure_mgmt_compute (0.11.0) + ms_rest_azure (~> 0.8.0) + azure_mgmt_network (0.11.0) + ms_rest_azure (~> 0.8.0) + azure_mgmt_resources (0.11.0) + ms_rest_azure (~> 0.8.0) + azure_mgmt_storage (0.11.0) + ms_rest_azure (~> 0.8.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) + 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_compute (~> 0.11.0) + azure_mgmt_network (~> 0.11.0) + azure_mgmt_resources (~> 0.11.0) + azure_mgmt_storage (~> 0.11.0) + climate_control + dotenv (~> 2.1) + haikunator (~> 1.1) + ms_rest_azure! + 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..3f78cbe 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,258 @@ -# Project Name +--- +services: compute +platforms: ruby +author: vishrutshah +--- -(short, 1-3 sentenced, description of the project) +# Create 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 virtual machine](#vm) + - [Stop the VM](#stop) + - [Start the VM](#start) + - [Restart the VM](#restart) + - [Delete the resources](#delete) + -* Feature 1 -* Feature 2 -* ... + +## Run this sample -## Getting Started +1. If you don't already have it, [install Ruby and the Ruby DevKit](https://www.ruby-lang.org/en/documentation/installation/). -### Prerequisites +2. If you don't have bundler, install it. -(ideally very short, if any) + ``` + gem install bundler + ``` + +3. Clone the repository. -- OS -- Library version -- ... + ``` + git clone https://github.com/Azure-Samples/compute-ruby-manage-vm.git + ``` -### Installation +4. Install the dependencies using bundle. -(ideally very short) + ``` + cd compute-ruby-manage-vm + bundle install + ``` + +5. Create an Azure service principal either through -- npm install [package name] -- mvn install -- ... + [Azure CLI](https://azure.microsoft.com/documentation/articles/resource-group-authenticate-service-principal-cli/), + [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/). -### Quickstart -(Add steps to get up and running quickly) +6. Set the following environment variables using the information from the service principle that you created. -1. git clone [repository clone url] -2. cd [respository name] -3. ... + ``` + 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} + ``` + > [AZURE.NOTE] On Windows, use `set` instead of `export`. -## Demo +7. Run the sample. -A demo app is included to show how to use the project. + ``` + bundle exec ruby example.rb + ``` -To run the demo, follow these steps: + +## What is example.rb doing? -(Add steps to start up the demo) +This sample starts by setting up ResourceManagementClient, the resource provider clients, a resource group, and a storage account using your subscription and credentials. -1. -2. -3. +```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 -## Resources +resource_group_params = Azure::ARM::Resources::Models::ResourceGroup.new.tap do |rg| + rg.location = REGION +end -(Any additional resources or related projects) +resource_group = resource_client.resource_groups.create_or_update(RESOURCE_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) +``` -- Link to supporting information -- Link to similar sample -- ... + +### Create a virtual network + +```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 + +```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 = 'sample-ruby-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 virtual machine + +```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 +) +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 +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) + vm +end +``` + + +### Stop the VM + +```ruby +compute_client.virtual_machines.power_off(GROUP_NAME, vm.name) +``` + + +### Start the VM + +```ruby +compute_client.virtual_machines.start(GROUP_NAME, vm.name) +``` + + +### Restart the VM + +```ruby +compute_client.virtual_machines.restart(GROUP_NAME, vm.name) +``` + + +### Delete the resources + +```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..b775c69 --- /dev/null +++ b/example.rb @@ -0,0 +1,258 @@ +#!/usr/bin/env ruby + +require 'azure_mgmt_resources' +require 'azure_mgmt_network' +require 'azure_mgmt_storage' +require 'azure_mgmt_compute' + +WEST_US = 'westus' +GROUP_NAME = 'azure-sample-compute-group' + +StorageModels = Azure::ARM::Storage::Models +NetworkModels = Azure::ARM::Network::Models +ComputeModels = Azure::ARM::Compute::Models +ResourceModels = Azure::ARM::Resources::Models + +# This sample shows how to manage a Azure virtual machines using 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 + # + MsRest.use_ssl_cert + 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 + + # + # 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) + + 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 = 'sample-ruby-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, 'firstvm', storage_account, vnet.subnets[0], public_ip) + + 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 "Connect to your new virtual machine that has Managed Service Identity extension running on localhost:50432 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 + + 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 + + vm_ext = compute_client.virtual_machine_extensions.create_or_update(GROUP_NAME, "sample-ruby-vm-#{vm_name}", ext_name, vm_extension) + + vm +end + +if $0 == __FILE__ + run_example +end From e4cc67266ab1cf209d9c1bc89daeb0279cfdc0d5 Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Tue, 22 Aug 2017 14:25:18 -0700 Subject: [PATCH 2/6] Updating README.md Adding sample to verify that MSI extension service is running --- .travis.yml | 10 -- CHANGELOG.md | 13 --- Gemfile | 1 - Gemfile.lock | 15 +-- README.md | 267 +++++++++++++++++++++++++++++---------------------- example.rb | 16 +-- 6 files changed, 167 insertions(+), 155 deletions(-) delete mode 100644 .travis.yml delete mode 100644 CHANGELOG.md diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4ad3bf5..0000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: ruby -sudo: false - -cache: - bundler: true - -rvm: - - 2.2.4 - - 2.3.0 - - ruby-head diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 9824752..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,13 +0,0 @@ -## [project-title] Changelog - - -# x.y.z (yyyy-mm-dd) - -*Features* -* ... - -*Bug Fixes* -* ... - -*Breaking Changes* -* ... diff --git a/Gemfile b/Gemfile index 1707dee..bf2ee8e 100644 --- a/Gemfile +++ b/Gemfile @@ -13,7 +13,6 @@ group :development, :test do gem 'climate_control' end -gem 'ms_rest_azure', path: '/Users/vishrut/git-repos/azure-sdk-for-ruby/runtime/ms_rest_azure' gem 'azure_mgmt_resources', '~>0.11.0' gem 'azure_mgmt_compute', '~>0.11.0' gem 'azure_mgmt_network', '~>0.11.0' diff --git a/Gemfile.lock b/Gemfile.lock index 4a8a64a..84d4447 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,12 +1,3 @@ -PATH - remote: /Users/vishrut/git-repos/azure-sdk-for-ruby/runtime/ms_rest_azure - specs: - ms_rest_azure (0.8.1) - concurrent-ruby (~> 1.0) - faraday (~> 0.9) - faraday-cookie_jar (~> 0.0.6) - ms_rest (~> 0.7.0) - GEM remote: https://rubygems.org/ specs: @@ -41,6 +32,11 @@ GEM concurrent-ruby (~> 1.0) faraday (~> 0.9) timeliness (~> 0.3) + ms_rest_azure (0.8.1) + 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) @@ -79,7 +75,6 @@ DEPENDENCIES climate_control dotenv (~> 2.1) haikunator (~> 1.1) - ms_rest_azure! rake (~> 11.1) rspec (~> 3.4) vcr (~> 3.0) diff --git a/README.md b/README.md index 3f78cbe..3f0a6f2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ platforms: ruby author: vishrutshah --- -# Create virtual machines with Managed Service Identity using Ruby +# 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. **On this page** @@ -13,12 +13,12 @@ This sample demonstrates how to create your Azure virtual machines with Managed - [What is example.rb doing?](#example) - [Create a virtual network](#vnet) - [Create a public IP address](#ipaddress) - - [Create a virtual machine](#vm) - - [Stop the VM](#stop) - - [Start the VM](#start) - - [Restart the VM](#restart) + - [Create a network interface](#nic) + - [Create a virtual machine with system identity](#vm) + - [Add MSI extension to the VM](#extension) + - [Verify MSI extension is running on VM by logging-in via ssh](#msi-extension) - [Delete the resources](#delete) - + ## Run this sample @@ -34,13 +34,13 @@ This sample demonstrates how to create your Azure virtual machines with Managed 3. Clone the repository. ``` - git clone https://github.com/Azure-Samples/compute-ruby-manage-vm.git + git clone https://github.com/Azure-Samples/compute-ruby-msi-vm.git ``` 4. Install the dependencies using bundle. ``` - cd compute-ruby-manage-vm + cd compute-ruby-msi-vm bundle install ``` @@ -74,44 +74,50 @@ This sample starts by setting up ResourceManagementClient, the resource provider ```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 - -resource_group_params = Azure::ARM::Resources::Models::ResourceGroup.new.tap do |rg| - rg.location = REGION -end - -resource_group = resource_client.resource_groups.create_or_update(RESOURCE_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| + 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 + + # + # 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 + sku.name = StorageModels::SkuName::PremiumLRS + sku.tier = StorageModels::SkuTier::Premium end - account.kind = StorageModels::Kind::Storage + 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 + 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) + end + print_item storage_account = storage_client.storage_accounts.create(GROUP_NAME, storage_account_name, storage_create_params) ``` @@ -120,19 +126,19 @@ print_item storage_account = storage_client.storage_accounts.create(GROUP_NAME, ```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 - ] + 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) ``` @@ -146,14 +152,14 @@ 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 = 'sample-ruby-domain-name-label' + 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 virtual machine + +### Create a network interface ```ruby print_item nic = network_client.network_interfaces.create_or_update( @@ -171,83 +177,116 @@ print_item nic = network_client.network_interfaces.create_or_update( ] end ) +``` + + +### Create a virtual machine with system identity + +```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' + 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 - 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 - ] + 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 + 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) - vm -end ``` - -### Stop the VM + +### Add MSI extension to the VM ```ruby -compute_client.virtual_machines.power_off(GROUP_NAME, vm.name) +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) ``` - -### Start the VM + +### Verify MSI extension is running on VM by logging-in via ssh -```ruby -compute_client.virtual_machines.start(GROUP_NAME, vm.name) ``` +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 - +... - -### Restart the VM - -```ruby -compute_client.virtual_machines.restart(GROUP_NAME, vm.name) +``` +``` +exit ``` diff --git a/example.rb b/example.rb index b775c69..451676c 100644 --- a/example.rb +++ b/example.rb @@ -6,14 +6,14 @@ require 'azure_mgmt_compute' WEST_US = 'westus' -GROUP_NAME = 'azure-sample-compute-group' +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 -# This sample shows how to manage a Azure virtual machines using using the Azure Resource Manager APIs for Ruby. +# 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: # @@ -26,7 +26,6 @@ def run_example # # Create the Resource Manager Client with an Application (service principal) token provider # - MsRest.use_ssl_cert subscription_id = ENV['AZURE_SUBSCRIPTION_ID'] || '11111111-1111-1111-1111-111111111111' # your Azure Subscription Id provider = MsRestAzure::ApplicationTokenProvider.new( ENV['AZURE_TENANT_ID'], @@ -96,12 +95,12 @@ def run_example ip.location = WEST_US ip.public_ipallocation_method = NetworkModels::IPAllocationMethod::Dynamic ip.dns_settings = NetworkModels::PublicIPAddressDnsSettings.new.tap do |dns| - dns.domain_name_label = 'sample-ruby-domain-name-label' + 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, 'firstvm', storage_account, vnet.subnets[0], public_ip) + vm = create_vm(compute_client, network_client, WEST_US, 'msi-vm', storage_account, vnet.subnets[0], public_ip) puts 'Listing all of the resources within the group' resource_client.resources.list_by_resource_group(GROUP_NAME).each do |res| @@ -111,7 +110,9 @@ def run_example export_template(resource_client) - puts "Connect to your new virtual machine that has Managed Service Identity extension running on localhost:50432 via: 'ssh -p 22 #{vm.os_profile.admin_username}@#{public_ip.dns_settings.fqdn}'. Admin Password is: Pa$$w0rd92" + 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 @@ -210,6 +211,7 @@ def create_vm(compute_client, network_client, location, vm_name, storage_acct, s ] end + # Use System Assigned Identity for the VM vm.identity = ComputeModels::VirtualMachineIdentity.new.tap do |identity| identity.type = ComputeModels::ResourceIdentityType::SystemAssigned end @@ -235,7 +237,7 @@ def create_vm(compute_client, network_client, location, vm_name, storage_acct, s 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..." + puts "Install Managed Service Identity Extension" ext_name = 'msiextension' vm_extension = ComputeModels::VirtualMachineExtension.new.tap do |extension| extension.publisher = 'Microsoft.ManagedIdentity' From de2dd47804a8347abdb0302437f1f403abc5897c Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Tue, 12 Sep 2017 14:13:55 -0700 Subject: [PATCH 3/6] Updating the README file with details on each steps --- CONTRIBUTING.md | 76 ------------------------------------------------- Gemfile | 8 +++--- Gemfile.lock | 26 ++++++++--------- README.md | 14 +++++++-- 4 files changed, 29 insertions(+), 95 deletions(-) delete mode 100644 CONTRIBUTING.md 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 index bf2ee8e..e994fd2 100644 --- a/Gemfile +++ b/Gemfile @@ -13,8 +13,8 @@ group :development, :test do gem 'climate_control' end -gem 'azure_mgmt_resources', '~>0.11.0' -gem 'azure_mgmt_compute', '~>0.11.0' -gem 'azure_mgmt_network', '~>0.11.0' -gem 'azure_mgmt_storage', '~>0.11.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 index 84d4447..c14a145 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,14 +3,14 @@ GEM specs: addressable (2.5.1) public_suffix (~> 2.0, >= 2.0.2) - azure_mgmt_compute (0.11.0) - ms_rest_azure (~> 0.8.0) - azure_mgmt_network (0.11.0) - ms_rest_azure (~> 0.8.0) - azure_mgmt_resources (0.11.0) - ms_rest_azure (~> 0.8.0) - azure_mgmt_storage (0.11.0) - ms_rest_azure (~> 0.8.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) @@ -32,7 +32,7 @@ GEM concurrent-ruby (~> 1.0) faraday (~> 0.9) timeliness (~> 0.3) - ms_rest_azure (0.8.1) + ms_rest_azure (0.9.0) concurrent-ruby (~> 1.0) faraday (~> 0.9) faraday-cookie_jar (~> 0.0.6) @@ -68,10 +68,10 @@ PLATFORMS ruby DEPENDENCIES - azure_mgmt_compute (~> 0.11.0) - azure_mgmt_network (~> 0.11.0) - azure_mgmt_resources (~> 0.11.0) - azure_mgmt_storage (~> 0.11.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) diff --git a/README.md b/README.md index 3f0a6f2..b1c70fb 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ This sample demonstrates how to create your Azure virtual machines with Managed ``` -## What is example.rb doing? +## What does example.rb doing? This sample starts by setting up ResourceManagementClient, the resource provider clients, a resource group, and a storage account using your subscription and credentials. @@ -122,6 +122,7 @@ subscription_id = ENV['AZURE_SUBSCRIPTION_ID'] || '11111111-1111-1111-1111-11111 ### 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' @@ -145,6 +146,7 @@ print_item vnet = network_client.virtual_networks.create_or_update(GROUP_NAME, ' ### 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' @@ -160,7 +162,8 @@ print_item public_ip = network_client.public_ipaddresses.create_or_update(GROUP_ ### 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, @@ -181,6 +184,8 @@ print_item nic = network_client.network_interfaces.create_or_update( ### 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' @@ -251,6 +256,7 @@ print_item vm = compute_client.virtual_machines.create_or_update(GROUP_NAME, "sa ### 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" @@ -271,6 +277,9 @@ vm_ext = compute_client.virtual_machine_extensions.create_or_update(GROUP_NAME, ### 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 @@ -291,6 +300,7 @@ exit ### Delete the resources +Now, we will delete all the resources create using this example. Please comment this out to keep the resources alive in you Azure subscription. ```ruby resource_client.resource_groups.delete(GROUP_NAME) From fca8e660e949342453d1eb5ba9a936abd624539f Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Tue, 12 Sep 2017 14:19:14 -0700 Subject: [PATCH 4/6] Adding CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7e36d76 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ + ## 2017.09.12 + + *Features* + * Adding initial sample to create Managed Service Identity Azure virtual machine. + \ No newline at end of file From 8db5a151be66e68cf4983a19b552aae15f7354b1 Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Tue, 12 Sep 2017 14:53:46 -0700 Subject: [PATCH 5/6] Fixing doc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1c70fb..974d145 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ exit ### Delete the resources -Now, we will delete all the resources create using this example. Please comment this out to keep the resources alive in you Azure subscription. +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) From cf5caaf26fa74447b97014426de8e1964e022f34 Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Tue, 12 Sep 2017 21:40:51 -0700 Subject: [PATCH 6/6] Adding role assignment section for MSI --- Gemfile | 11 ++++++----- Gemfile.lock | 3 +++ README.md | 32 +++++++++++++++++++++++++++++++- example.rb | 25 ++++++++++++++++++++++--- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index e994fd2..1fc1bb0 100644 --- a/Gemfile +++ b/Gemfile @@ -13,8 +13,9 @@ group :development, :test do gem 'climate_control' end -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' +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 index c14a145..a25ed63 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,6 +3,8 @@ GEM 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) @@ -68,6 +70,7 @@ 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) diff --git a/README.md b/README.md index 974d145..80ee816 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ This sample demonstrates how to create your Azure virtual machines with Managed - [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) @@ -46,10 +47,13 @@ This sample demonstrates how to create your Azure virtual machines with Managed 5. Create an Azure service principal either through - [Azure CLI](https://azure.microsoft.com/documentation/articles/resource-group-authenticate-service-principal-cli/), + [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/). + :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. ``` @@ -275,6 +279,32 @@ 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 diff --git a/example.rb b/example.rb index 451676c..2c99cd7 100644 --- a/example.rb +++ b/example.rb @@ -4,6 +4,7 @@ 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' @@ -12,6 +13,7 @@ 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. # @@ -40,6 +42,8 @@ def run_example 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 @@ -50,7 +54,7 @@ def run_example # Create Resource group puts 'Create Resource Group' - print_group resource_client.resource_groups.create_or_update(GROUP_NAME, resource_group_params) + print_group resource_group = resource_client.resource_groups.create_or_update(GROUP_NAME, resource_group_params) postfix = rand(1000) storage_account_name = "rubystor#{postfix}" @@ -102,6 +106,21 @@ def run_example 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 @@ -250,9 +269,9 @@ def create_vm(compute_client, network_client, location, vm_name, storage_acct, s 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) + compute_client.virtual_machine_extensions.create_or_update(GROUP_NAME, "sample-ruby-vm-#{vm_name}", ext_name, vm_extension) - vm + compute_client.virtual_machines.get(GROUP_NAME, "sample-ruby-vm-#{vm_name}") end if $0 == __FILE__