Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .expeditor/verify.pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ steps:
docker:
image: ruby:3.1-buster

- label: run-specs-ruby-3.4
command:
- .expeditor/run_linux_tests.sh rake
expeditor:
executor:
docker:
image: ruby:3.4-bookworm

- label: run-specs-windows
command:
- bundle config --local path vendor/bundle
Expand All @@ -30,3 +38,15 @@ steps:
docker:
host_os: windows
image: rubydistros/windows-2019:3.1

- label: run-specs-windows-ruby-3.4
command:
- bundle config --local path vendor/bundle
- bundle config set --local without docs debug
- bundle install --jobs=7 --retry=3
- bundle exec rake spec
expeditor:
executor:
docker:
host_os: windows
image: rubydistros/windows-2019:3.4
89 changes: 89 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,37 @@ This document provides comprehensive guidance for GitHub Copilot when working wi

## ⚡ Quick Start for GitHub Copilot

### 🚨 CRITICAL MANDATORY REQUIREMENTS - NEVER SKIP THESE
**⚠️ FAILURE TO FOLLOW THESE WILL CAUSE BUILD FAILURES AND COMPLIANCE VIOLATIONS ⚠️**

#### 🔴 1. DCO SIGNOFF - EVERY SINGLE COMMIT
```bash
# MANDATORY: Use --signoff for EVERY commit
git commit --signoff -m "Your commit message"
# Builds WILL FAIL without DCO signoff
```

#### 🔴 2. AI COMPLIANCE - IMMEDIATE AFTER PR CREATION
```bash
# MANDATORY: Update Jira field IMMEDIATELY after PR creation
# Use atlassian-mcp tools to update customfield_11170 = "Yes"
# This is REQUIRED for Progress AI governance
```

#### 🔴 3. AI-ASSISTED LABEL - NON-NEGOTIABLE
```bash
# MANDATORY: All PRs MUST have ai-assisted label
gh pr create --label "ai-assisted"
# If label doesn't exist, create it first:
gh label create "ai-assisted" --description "Work completed with AI assistance following Progress AI policies" --color "9A4DFF"
```

#### 🔴 4. TEST COVERAGE >80% - BUILD REQUIREMENT
```bash
# MANDATORY: All code changes must achieve >80% test coverage
bundle exec rake spec # Must pass with >80% coverage
```

### 🎯 CRITICAL SUCCESS FACTORS
1. **>80% Test Coverage** - NON-NEGOTIABLE for all code changes
2. **AI Compliance** - ALL PRs MUST include `ai-assisted` label and Jira field updates
Expand Down Expand Up @@ -438,6 +469,42 @@ EOF
)" --label "Type: Enhancement" --label "Platform: Azure" --label "ai-assisted"
```

### 🚨 MANDATORY POST-PR CHECKLIST - DO NOT SKIP!
**⚠️ COMPLETE THESE STEPS IMMEDIATELY AFTER PR CREATION ⚠️**

#### ✅ **Step 1: Verify DCO Signoff**
```bash
# Check if commit has DCO signoff
git log --format=fuller -1
# Look for: "Signed-off-by: Your Name <your.email@example.com>"
# If missing, fix with: git commit --amend --signoff --no-edit
```

#### ✅ **Step 2: Verify ai-assisted Label**
```bash
# If label doesn't exist, create it:
gh label create "ai-assisted" --description "Work completed with AI assistance following Progress AI policies" --color "9A4DFF"
# Verify PR has the label applied
```

#### ✅ **Step 3: Update Jira Field (MANDATORY)**
```bash
# IMMEDIATELY update customfield_11170 to "Yes"
# Use atlassian-mcp tools with correct format:
# {"customfield_11170": {"value": "Yes"}}
```

#### ✅ **Step 4: Final Verification**
```bash
# Verify all compliance requirements met:
# - DCO signoff present ✅
# - ai-assisted label applied ✅
# - Jira field updated ✅
# - >80% test coverage ✅
```

**🚫 DO NOT PROCEED WITHOUT COMPLETING ALL 4 STEPS ABOVE!**

### PR Description Template
Use this HTML-formatted template for all PRs:

Expand Down Expand Up @@ -735,6 +802,15 @@ gh pr create --label "Type: Bug" --label "Aspect: Security" --label "Priority: C

## Prompt-Based Execution Protocol

### 🚨 MANDATORY COMPLIANCE REMINDERS FOR EVERY TASK

**Before starting ANY task, ALWAYS remind yourself of these NON-NEGOTIABLE requirements:**

1. **🔴 DCO Signoff**: EVERY commit needs `git commit --signoff`
2. **🔴 Test Coverage**: ALL code changes need >80% test coverage
3. **🔴 AI Labels**: ALL PRs need `ai-assisted` label
4. **🔴 Jira Field**: IMMEDIATELY update `customfield_11170` = "Yes" after PR creation

### Execution Flow

All tasks must follow this interactive pattern:
Expand Down Expand Up @@ -820,6 +896,19 @@ Currently working on: Repository structure analysis
❓ Do you want me to continue with the implementation planning?"
```

#### 6. Final Compliance Verification
At the end of ANY implementation, ALWAYS verify:
```
"🎯 FINAL COMPLIANCE CHECK:
✅ DCO Signoff: [Check if all commits are signed]
✅ Test Coverage: [Verify >80% coverage achieved]
✅ AI Labels: [Confirm ai-assisted label applied]
✅ Jira Field: [Verify customfield_11170 = 'Yes']

📋 All compliance requirements satisfied? [Yes/No]
If No, identify and fix missing items before completing."

Copilot AI Nov 11, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Corrected capitalization: 'If No' should be 'If no' for consistency with standard sentence formatting.

Suggested change
If No, identify and fix missing items before completing."
If no, identify and fix missing items before completing."

Copilot uses AI. Check for mistakes.
```

---

## Repository-Specific Guidelines
Expand Down
5 changes: 3 additions & 2 deletions knife-azure.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ Gem::Specification.new do |s|

s.add_dependency "knife", ">= 18.0"
s.add_dependency "nokogiri", ">= 1.5.5"
s.add_dependency "azure_mgmt_resources", "~> 0.17", ">= 0.17.2"
s.add_dependency "azure_mgmt_compute", "~> 0.18", ">= 0.18.3"
s.add_dependency "azure_mgmt_storage", "~> 0.20", ">= 0.20.0"
s.add_dependency "azure_mgmt_network", "~> 0.18", ">= 0.18.2"
s.add_dependency "azure_mgmt_network2", "~> 1.0.1", ">= 1.0.1"
s.add_dependency "azure_mgmt_resources2", "~> 1.0.1", ">= 1.0.1"
s.add_dependency "listen", "~> 3.1"
s.add_dependency "ipaddress"
s.add_dependency "ffi"
s.add_dependency "rb-readline"
Comment thread
ashiqueps marked this conversation as resolved.
s.add_dependency "abbrev"
end
31 changes: 15 additions & 16 deletions lib/azure/resource_management/ARM_deployment_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -323,15 +323,16 @@ def create_deployment_template(params)
"sshKeyPath" => "[concat('/home/',parameters('adminUserName'),'/.ssh/authorized_keys')]",
},
"resources" => [
{
"type" => "Microsoft.Storage/storageAccounts",
"name" => "[variables('storageAccountName')]",
"apiVersion" => "[variables('apiVersion')]",
"location" => "[resourceGroup().location]",
"properties" => {
"accountType" => "[variables('storageAccountType')]",
},
},
# Storage account not needed for managed disks
Comment thread
ashiqueps marked this conversation as resolved.
Outdated
# {
# "type" => "Microsoft.Storage/storageAccounts",
# "name" => "[variables('storageAccountName')]",
# "apiVersion" => "[variables('apiVersion')]",
# "location" => "[resourceGroup().location]",
# "properties" => {
# "accountType" => "[variables('storageAccountType')]",
# },
# },
Comment thread
ashiqueps marked this conversation as resolved.
Outdated
{
"apiVersion" => "[variables('apiVersion')]",
"type" => "Microsoft.Network/publicIPAddresses",
Expand Down Expand Up @@ -391,7 +392,7 @@ def create_deployment_template(params)
},
},
{
Comment thread
ashiqueps marked this conversation as resolved.
"apiVersion" => "[variables('apiVersion')]",
"apiVersion" => "2017-03-30",
"type" => "Microsoft.Compute/virtualMachines",
"name" => vmName,
"location" => "[resourceGroup().location]",
Expand All @@ -400,7 +401,6 @@ def create_deployment_template(params)
"count" => "[parameters('numberOfInstances')]",
},
"dependsOn" => [
"[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]",
depVm2,
],
"properties" => {
Expand Down Expand Up @@ -431,11 +431,11 @@ def create_deployment_template(params)
},
"osDisk" => {
"name" => "[variables('OSDiskName')]",
"vhd" => {
"uri" => uri,
},
"caching" => "ReadWrite",
"createOption" => "FromImage",
"managedDisk" => {
"storageAccountType" => "Standard_LRS",
},
},
},
"networkProfile" => {
Expand All @@ -447,8 +447,7 @@ def create_deployment_template(params)
},
"diagnosticsProfile" => {
"bootDiagnostics" => {
Comment thread
ashiqueps marked this conversation as resolved.
"enabled" => "true",
"storageUri" => "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net')]",
"enabled" => "false",
Comment thread
ashiqueps marked this conversation as resolved.
},
},
},
Expand Down
58 changes: 40 additions & 18 deletions lib/azure/resource_management/ARM_interface.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,69 +18,83 @@
require_relative "../azure_interface"
require_relative "ARM_deployment_template"
require_relative "vnet_config"
require "azure_mgmt_resources"
require "azure_mgmt_resources2"
require "azure_mgmt_compute"
require "azure_mgmt_storage"
require "azure_mgmt_network"
require "azure_mgmt_network2"
require "ms_rest_azure"
require "ms_rest_azure2"
require "ms_rest"
require "ms_rest2"

module Azure
class ResourceManagement
class ARMInterface < AzureInterface
include Azure::ARM::ARMDeploymentTemplate
include Azure::ARM::VnetConfig

include Azure::Resources::Mgmt::V2018_05_01
include Azure::Resources::Mgmt::V2018_05_01::Models
include Azure::Resources2::Mgmt::V2018_05_01
include Azure::Resources2::Mgmt::V2018_05_01::Models

include Azure::Compute::Mgmt::V2018_06_01
include Azure::Compute::Mgmt::V2018_06_01::Models

include Azure::Storage::Mgmt::V2018_07_01
include Azure::Storage::Mgmt::V2018_07_01::Models

include Azure::Network::Mgmt::V2018_08_01
include Azure::Network::Mgmt::V2018_08_01::Models
include Azure::Network2::Mgmt::V2018_08_01
include Azure::Network2::Mgmt::V2018_08_01::Models

attr_accessor :connection

def initialize(params = {})
token_provider = if params[:azure_client_secret]
MsRestAzure::ApplicationTokenProvider.new(params[:azure_tenant_id], params[:azure_client_id], params[:azure_client_secret])
else
MsRest::StringTokenProvider.new(params[:token], params[:tokentype])
end
@credentials = MsRest::TokenCredentials.new(token_provider)
# Create credentials for original gems (compute, storage)
token_provider_v1 = if params[:azure_client_secret]
MsRestAzure::ApplicationTokenProvider.new(params[:azure_tenant_id], params[:azure_client_id], params[:azure_client_secret])
else
MsRest::StringTokenProvider.new(params[:token], params[:tokentype])
end
@credentials_v1 = MsRest::TokenCredentials.new(token_provider_v1)

# Create credentials for forked gems (resources2, network2)
token_provider_v2 = if params[:azure_client_secret]
MsRestAzure2::ApplicationTokenProvider.new(params[:azure_tenant_id], params[:azure_client_id], params[:azure_client_secret])
else
MsRest2::StringTokenProvider.new(params[:token], params[:tokentype])
end
@credentials_v2 = MsRest2::TokenCredentials.new(token_provider_v2)
Comment thread
ashiqueps marked this conversation as resolved.
Outdated

@azure_subscription_id = params[:azure_subscription_id]
super
end

def resource_management_client
@resource_management_client ||= begin
resource_management_client = ResourceManagementClient.new(@credentials)
resource_management_client = ResourceManagementClient.new(@credentials_v2)
resource_management_client.subscription_id = @azure_subscription_id
resource_management_client
end
end

def compute_management_client
@compute_management_client ||= begin
compute_management_client = ComputeManagementClient.new(@credentials)
compute_management_client = ComputeManagementClient.new(@credentials_v1)
compute_management_client.subscription_id = @azure_subscription_id
compute_management_client
end
end

def storage_management_client
@storage_management_client ||= begin
storage_management_client = StorageManagementClient.new(@credentials)
storage_management_client = StorageManagementClient.new(@credentials_v1)
storage_management_client.subscription_id = @azure_subscription_id
storage_management_client
end
end

def network_resource_client
@network_resource_client ||= begin
network_resource_client = NetworkManagementClient.new(@credentials)
network_resource_client = NetworkManagementClient.new(@credentials_v2)
network_resource_client.subscription_id = @azure_subscription_id
network_resource_client
end
Expand Down Expand Up @@ -226,13 +240,21 @@ def security_group_exist?(resource_group_name, security_group_name)
network_resource_client.network_security_groups.get(resource_group_name, security_group_name)
true
rescue MsRestAzure::AzureOperationError => e
error_body = nil
if e.body
err_json = JSON.parse(e.response.body)
error_body = e.body
elsif e.response && e.response.body
error_body = e.response.body
end
if error_body
err_json = JSON.parse(error_body)
if err_json["error"]["code"] == "ResourceNotFound"
false
else
raise e
end
else
raise e
end
end

Expand Down Expand Up @@ -479,7 +501,7 @@ def delete_resource_group(resource_group_name)
end

def common_arm_rescue_block(error)
if error.class == MsRestAzure::AzureOperationError && error.body
if (error.class == MsRestAzure::AzureOperationError || error.class == MsRestAzure2::AzureOperationError) && error.body
Comment thread
ashiqueps marked this conversation as resolved.
Outdated
Comment thread
ashiqueps marked this conversation as resolved.
Outdated
err_json = JSON.parse(error.response.body)
err_details = err_json["error"]["details"] if err_json["error"]
if err_details
Expand Down
2 changes: 1 addition & 1 deletion lib/azure/resource_management/vnet_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def subnets_list_for_specific_address_space(address_prefix, subnets_list)

def get_vnet(resource_group_name, vnet_name)
network_resource_client.virtual_networks.get(resource_group_name, vnet_name)
rescue MsRestAzure::AzureOperationError => error
rescue MsRestAzure2::AzureOperationError => error
if error.body
err_json = JSON.parse(error.response.body)
if err_json["error"]["code"] == "ResourceNotFound"
Expand Down
6 changes: 6 additions & 0 deletions lib/chef/knife/azurerm_server_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ def create_server_def
server_def[:azure_storage_account] = config[:azure_vm_name] if server_def[:azure_storage_account].nil?
server_def[:azure_storage_account] = server_def[:azure_storage_account].gsub(/[!@#$%^&*()_-]/, "")

# Ensure storage account name + Azure uniquestring prefix stays under 24 chars
# Azure uniquestring() generates ~13 characters, so we limit to 10 chars max
if server_def[:azure_storage_account].length > 10
server_def[:azure_storage_account] = server_def[:azure_storage_account][0, 10]
end
Comment thread
ashiqueps marked this conversation as resolved.
Outdated
Comment thread
ashiqueps marked this conversation as resolved.
Outdated

server_def[:azure_os_disk_name] = config[:azure_vm_name] if server_def[:azure_os_disk_name].nil?
server_def[:azure_os_disk_name] = server_def[:azure_os_disk_name].gsub(/[!@#$%^&*()_-]/, "")

Expand Down
Loading