Skip to content

Commit 380dc58

Browse files
committed
update
1 parent d070e4a commit 380dc58

File tree

1 file changed

+248
-1
lines changed
  • src/azure-cli/azure/cli/command_modules/acs

1 file changed

+248
-1
lines changed

src/azure-cli/azure/cli/command_modules/acs/custom.py

Lines changed: 248 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2147,10 +2147,19 @@ def k8s_install_kubectl(cmd, client_version='latest', install_location=None, sou
21472147
"""
21482148

21492149
if not source_url:
2150-
source_url = "https://dl.k8s.io/release"
21512150
cloud_name = cmd.cli_ctx.cloud.name
21522151
if cloud_name.lower() == 'azurechinacloud':
21532152
source_url = 'https://mirror.azure.cn/kubernetes/kubectl'
2153+
else:
2154+
# Only try Microsoft packages for Linux systems (packages are Linux-only)
2155+
system = platform.system()
2156+
if system == 'Linux':
2157+
try:
2158+
return _k8s_install_kubectl_from_microsoft_packages(cmd, client_version, install_location, arch)
2159+
except Exception as e:
2160+
logger.warning("Failed to install from Microsoft packages, falling back to Google Storage: %s", str(e))
2161+
# For non-Linux systems or fallback, use Google Storage
2162+
source_url = "https://storage.googleapis.com/kubernetes-release/release"
21542163

21552164
if client_version == 'latest':
21562165
latest_version_url = source_url + '/stable.txt'
@@ -2205,6 +2214,244 @@ def k8s_install_kubectl(cmd, client_version='latest', install_location=None, sou
22052214
install_dir, cli)
22062215

22072216

2217+
def _k8s_install_kubectl_from_microsoft_packages(cmd, client_version='latest', install_location=None, arch=None):
2218+
"""
2219+
Install kubectl from Microsoft packages repository by downloading and extracting .deb package.
2220+
Note: This method is only supported on Linux systems as Microsoft packages contain Linux binaries.
2221+
"""
2222+
system = platform.system()
2223+
if system != 'Linux':
2224+
raise CLIError(f"Microsoft packages method is only supported on Linux (current system: {system}). Use '--source-url' to specify an alternative source.")
2225+
2226+
if arch is None:
2227+
arch = get_arch_for_cli_binary()
2228+
2229+
# Map architecture to package architecture
2230+
if arch == 'amd64':
2231+
pkg_arch = 'amd64'
2232+
elif arch == 'arm64':
2233+
pkg_arch = 'arm64'
2234+
else:
2235+
raise CLIError(f"Unsupported architecture '{arch}' for Microsoft packages")
2236+
2237+
# Get available kubectl packages from Microsoft
2238+
packages_url = f"https://packages.microsoft.com/ubuntu/22.04/prod/dists/jammy/main/binary-{pkg_arch}/Packages.gz"
2239+
2240+
logger.warning('Fetching kubectl package information from Microsoft packages repository...')
2241+
try:
2242+
packages_data = _urlopen_read(packages_url)
2243+
import gzip
2244+
packages_text = gzip.decompress(packages_data).decode('utf-8')
2245+
except Exception as e:
2246+
raise CLIError(f'Failed to fetch package information from Microsoft: {e}')
2247+
2248+
# Parse packages and find kubectl
2249+
kubectl_packages = []
2250+
current_package = {}
2251+
2252+
for line in packages_text.split('\n'):
2253+
if line.startswith('Package: '):
2254+
if current_package.get('Package') == 'kubectl':
2255+
kubectl_packages.append(current_package)
2256+
current_package = {'Package': line.split(': ', 1)[1]}
2257+
elif line.startswith('Version: '):
2258+
current_package['Version'] = line.split(': ', 1)[1]
2259+
elif line.startswith('Filename: '):
2260+
current_package['Filename'] = line.split(': ', 1)[1]
2261+
elif line.startswith('SHA256: '):
2262+
current_package['SHA256'] = line.split(': ', 1)[1]
2263+
elif line == '':
2264+
if current_package.get('Package') == 'kubectl':
2265+
kubectl_packages.append(current_package)
2266+
current_package = {}
2267+
2268+
if not kubectl_packages:
2269+
raise CLIError('No kubectl packages found in Microsoft repository')
2270+
2271+
# Select package version
2272+
if client_version == 'latest':
2273+
# Sort by version and get latest (simple string sort should work for semver)
2274+
kubectl_packages.sort(key=lambda x: x.get('Version', ''), reverse=True)
2275+
selected_package = kubectl_packages[0]
2276+
logger.warning('Using latest kubectl version from Microsoft packages: %s', selected_package['Version'])
2277+
else:
2278+
# Find specific version
2279+
version_to_find = client_version.lstrip('v') # Remove 'v' prefix if present
2280+
selected_package = None
2281+
for pkg in kubectl_packages:
2282+
if pkg.get('Version', '').startswith(version_to_find):
2283+
selected_package = pkg
2284+
break
2285+
if not selected_package:
2286+
raise CLIError(f'kubectl version {client_version} not found in Microsoft packages')
2287+
2288+
# Download the .deb package
2289+
base_url = "https://packages.microsoft.com/ubuntu/22.04/prod/"
2290+
package_url = base_url + selected_package['Filename']
2291+
2292+
logger.warning('Downloading kubectl package from Microsoft: %s', package_url)
2293+
2294+
# Create temporary directory for package extraction
2295+
with tempfile.TemporaryDirectory() as temp_dir:
2296+
deb_path = os.path.join(temp_dir, 'kubectl.deb')
2297+
2298+
try:
2299+
_urlretrieve(package_url, deb_path)
2300+
except Exception as e:
2301+
raise CLIError(f'Failed to download kubectl package: {e}')
2302+
2303+
# Extract .deb package using ar and tar
2304+
try:
2305+
_extract_kubectl_from_deb(deb_path, temp_dir, install_location, system)
2306+
except Exception as e:
2307+
raise CLIError(f'Failed to extract kubectl from package: {e}')
2308+
2309+
# Handle post-installation
2310+
install_dir, cli = os.path.dirname(install_location), os.path.basename(install_location)
2311+
if system == 'Windows':
2312+
handle_windows_post_install(install_dir, cli)
2313+
else:
2314+
logger.warning('Please ensure that %s is in your search PATH, so the `%s` command can be found.',
2315+
install_dir, cli)
2316+
2317+
logger.warning('Successfully installed kubectl from Microsoft packages')
2318+
2319+
2320+
def _extract_kubectl_from_deb(deb_path, temp_dir, install_location, system):
2321+
"""
2322+
Extract kubectl binary from .deb package.
2323+
"""
2324+
import subprocess
2325+
2326+
# Ensure installation directory exists
2327+
install_dir = os.path.dirname(install_location)
2328+
if not os.path.exists(install_dir):
2329+
os.makedirs(install_dir)
2330+
2331+
# Determine binary name
2332+
if system == 'Windows':
2333+
binary_name = 'kubectl.exe'
2334+
else:
2335+
binary_name = 'kubectl'
2336+
2337+
# validate install location
2338+
validate_install_location(install_location, binary_name)
2339+
2340+
# Extract .deb using ar command or dpkg-deb if available
2341+
try:
2342+
# Try using dpkg-deb first (handles all compression formats)
2343+
if shutil.which('dpkg-deb'):
2344+
subprocess.run(['dpkg-deb', '-x', deb_path, temp_dir], check=True, capture_output=True)
2345+
elif shutil.which('ar'):
2346+
# Extract using ar command
2347+
subprocess.run(['ar', 'x', deb_path], cwd=temp_dir, check=True, capture_output=True)
2348+
2349+
# Find and extract the data archive
2350+
data_files = [f for f in os.listdir(temp_dir) if f.startswith('data.tar')]
2351+
if not data_files:
2352+
raise CLIError('No data archive found in .deb package')
2353+
2354+
data_file = os.path.join(temp_dir, data_files[0])
2355+
2356+
# Handle different compression formats
2357+
if data_files[0].endswith('.xz'):
2358+
import lzma
2359+
with lzma.open(data_file, 'rb') as f_in:
2360+
with open(os.path.join(temp_dir, 'data.tar'), 'wb') as f_out:
2361+
shutil.copyfileobj(f_in, f_out)
2362+
data_file = os.path.join(temp_dir, 'data.tar')
2363+
elif data_files[0].endswith('.gz'):
2364+
import gzip
2365+
with gzip.open(data_file, 'rb') as f_in:
2366+
with open(os.path.join(temp_dir, 'data.tar'), 'wb') as f_out:
2367+
shutil.copyfileobj(f_in, f_out)
2368+
data_file = os.path.join(temp_dir, 'data.tar')
2369+
elif data_files[0].endswith('.zst'):
2370+
# Try using zstd command if available
2371+
if shutil.which('zstd'):
2372+
uncompressed_file = os.path.join(temp_dir, 'data.tar')
2373+
subprocess.run(['zstd', '-d', data_file, '-o', uncompressed_file], check=True, capture_output=True)
2374+
data_file = uncompressed_file
2375+
else:
2376+
logger.warning('zstd compression detected but zstd command not found. Please install zstd: brew install zstd (macOS) or apt install zstd (Ubuntu)')
2377+
raise CLIError('zstd compression not supported (zstd command not found)')
2378+
2379+
# Extract tar archive
2380+
import tarfile
2381+
with tarfile.open(data_file, 'r') as tar:
2382+
tar.extractall(temp_dir)
2383+
else:
2384+
# Fallback: Use Python to extract .deb (it's an ar archive)
2385+
_extract_ar_archive(deb_path, temp_dir)
2386+
2387+
# This method is complex for newer compression formats, so we'll try a simpler approach
2388+
raise CLIError('No suitable extraction tool found (dpkg-deb, ar). Please install dpkg-deb or ar.')
2389+
2390+
# Find kubectl binary in extracted files
2391+
kubectl_path = None
2392+
for root, dirs, files in os.walk(temp_dir):
2393+
for file in files:
2394+
if file == 'kubectl' and os.access(os.path.join(root, file), os.X_OK):
2395+
kubectl_path = os.path.join(root, file)
2396+
break
2397+
if kubectl_path:
2398+
break
2399+
2400+
if not kubectl_path:
2401+
raise CLIError('kubectl binary not found in extracted package')
2402+
2403+
# Copy to final location
2404+
shutil.copy2(kubectl_path, install_location)
2405+
2406+
# Set executable permissions
2407+
os.chmod(install_location,
2408+
os.stat(install_location).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
2409+
2410+
except subprocess.CalledProcessError as e:
2411+
stderr = e.stderr.decode('utf-8') if e.stderr else str(e)
2412+
raise CLIError(f'Failed to extract .deb package: {stderr}')
2413+
except Exception as e:
2414+
raise CLIError(f'Error during package extraction: {e}')
2415+
2416+
2417+
def _extract_ar_archive(ar_path, extract_dir):
2418+
"""
2419+
Simple Python implementation to extract .deb (ar archive) files.
2420+
"""
2421+
with open(ar_path, 'rb') as f:
2422+
# Check for ar archive signature
2423+
signature = f.read(8)
2424+
if signature != b'!<arch>\n':
2425+
raise CLIError('Invalid .deb file format')
2426+
2427+
while True:
2428+
# Read file header (60 bytes)
2429+
header = f.read(60)
2430+
if len(header) < 60:
2431+
break
2432+
2433+
# Parse header
2434+
filename = header[0:16].decode('ascii').strip()
2435+
size = int(header[48:58].decode('ascii').strip())
2436+
2437+
# Skip debian-binary
2438+
if filename == 'debian-binary':
2439+
f.read(size)
2440+
if size % 2: # ar archives are padded to even boundaries
2441+
f.read(1)
2442+
continue
2443+
2444+
# Read file content
2445+
content = f.read(size)
2446+
if size % 2: # ar archives are padded to even boundaries
2447+
f.read(1)
2448+
2449+
# Write to extract directory
2450+
output_path = os.path.join(extract_dir, filename.rstrip('/'))
2451+
with open(output_path, 'wb') as out_f:
2452+
out_f.write(content)
2453+
2454+
22082455
# install kubelogin
22092456
def k8s_install_kubelogin(cmd, client_version='latest', install_location=None, source_url=None, arch=None):
22102457
"""

0 commit comments

Comments
 (0)