I currently have a few VMs (compute instances) running in OCI and it’s getting annoying having to manually use the console to stopthese when I no longer need them, I put together the Python script below which uses the OCI Python SDK to stop all running instances within a specific compartment.
If you haven’t used the OCI Python SDK before, I put together a quick guide on getting started here.
import oci
import json
# Read the config
config = oci.config.from_file()
# Create client with the default config file (\.oci\config)
computeclient = oci.core.ComputeClient(config)
# List all of the compute instances within the compartment
list_instances_response = computeclient.list_instances(
compartment_id="SPECIFY THE COMPARTMENT ID")
# Convert to JSON
instances = json.loads(str(list_instances_response.data))
# Print the state of each VM and stop any that are running
for instance in instances:
print(instance["display_name"] + " - status: " + instance["lifecycle_state"])
if instance["lifecycle_state"] == "RUNNING":
print("Stopping :" + instance["display_name"])
computeclient.instance_action(instance["id"],"SOFTSTOP")
This uses the SOFTSTOP action which does the following:
Gracefully shuts down the instance by sending a shutdown command to the operating system. After waiting 15 minutes for the OS to shut down, the instance is powered off. If the applications that run on the instance take more than 15 minutes to shut down, they could be improperly stopped, resulting in data corruption. To avoid this, manually shut down the instance using the commands available in the OS before you softstop the instance.
I previously posted a guide on how to use OCI Bastion to connect to a Windows VM using RDP, I’ve also been playing around with connecting to Linux VMs via OCI Bastion using SSH.
As Bastion sessions have a maximum session TTL of 3 hours this means that in a given day I’d need to go through the process of creating a new Bastion session multiple times if I needed continued access to the VM. As I’m lazy, I’ve put together a Python script that automates Bastion session creation for Managed SSH sessions which does the following:
Checks the active sessions on a given OCI Bastion ๐
If there isn’t an active session for the specified server, create a new managed SSH sessions and return the SSH command to connect (copies to the clipboard)
If there is an active session, simply return the SSH command to connect (copies to the clipboard)
When the script has run, the SSH command to connect to the VM is in the clipboard so simply paste this into a terminal to connect.
The script is a little rough around the edges but it get’s the job done, I’ll likely optimise it over time – including making it easier to connect to multiple servers via multiple Bastions.
To run the script you’ll need the following information:
OCID of the Bastion to use โ
OCID of the server (VM) to connect to โ
The path to the private key used for Bastion and SSH authentication โ
The contents of the associated public key โ
The script can be found below and is also available on GitHub.
# This script queries the active sessions on a given OCI bastion (bastionocid), if it doesn't find a session for the specified server (serverocid)
# it will create a new bastion session and returns the resultant SSH command to connect to the server and copies to the clipboard, simply paste this into a terminal to connect,
# if their is an active session for the server, it returns the SSH command to connect to the server and copies to the clipboard, simply paste into a terminal to connect
# pip install oci - if you don't have this installed
import oci
# pip install pyperclip - if you don't have this installed
import pyperclip
import json
import time
# OCID of the bastion to query
bastionocid = "BASTIONOCID"
# OCID of the server to connect to
server = "SERVEROCID"
# Private key
privatekeypath = "D:\\OCI\\OCI.key" # Update to the correct location of the private key, this script was developed on Windows, hence the double slashes.
# Public key used for auth to bastion (using the same key for the VM too, to make it simple)
publickey = "PUBLICKEY" # Copy/paste the content of the public key file (.pub)
# Read the config
config = oci.config.from_file()
# Create client with the default config file (\.oci\config)
bastionclient = oci.bastion.BastionClient(config)
# List the current sessions
bastionsessions = bastionclient.list_sessions(bastion_id = bastionocid)
sessionsJSON = json.loads(str((bastionsessions.data)))
# See if there is an active session for the server, if there isn't create one
for session in sessionsJSON:
if session["lifecycle_state"] == "ACTIVE" and session["target_resource_details"]["target_resource_id"] == server:
print("Active Session Found : " + session["display_name"])
# Get the session and return the ssh command generated
getsession = bastionclient.get_session(session_id = session["id"])
sshcmd = getsession.data.ssh_metadata["command"]
# Automate connecting - replace the placeholder for private key with the correct location
sshcmd = sshcmd.replace("<privateKey>",privatekeypath)
# Copy the SSH command to the clipboard so that it can be pasted into PowerShell
pyperclip.copy(sshcmd)
break
else:
print("No active session found - creating a session.....")
create_session_response = bastionclient.create_session(
create_session_details=oci.bastion.models.CreateSessionDetails(
bastion_id=bastionocid,target_resource_details=oci.bastion.models.CreateManagedSshSessionTargetResourceDetails(
session_type="MANAGED_SSH",target_resource_id=server,target_resource_operating_system_user_name="opc"),
key_details=oci.bastion.models.PublicKeyDetails(public_key_content=publickey),session_ttl_in_seconds = 10800,
))
# Get the session and return the ssh command generated
time.sleep(120)
session = bastionclient.get_session(session_id = create_session_response.data.id)
sshcmd = session.data.ssh_metadata["command"]
# Automate connecting - replace the placeholder for private key with the correct location
sshcmd = sshcmd.replace("<privateKey>",privatekeypath)
# Copy the SSH command to the clipboard so that it can be pasted into PowerShell
pyperclip.copy(sshcmd)
print("Session created, paste the resultant SSH command from the clipboard")
break
I’ve been working with OCI recently and have just started to write some Python scripts that use the OCI SDK. I thought I’d document the process of getting everything setup – mainly because I’ll probably forget how I did this and have to re-learn at some point in the future ๐.
This guide was written for Windows 11, however with minor adaptions should work with any OS that supports the OCI SDK for Python.
Step 1 – Install the OCI Python SDK
Run the following command to install the OCI Python SDK.
Step 3 – Connect to OCI with Python using the API Signing Key
Now that the OCI SDK for Python is installed and an API Signing Key has been created, you can connect to OCI using the following:
import oci
# Reads the config created when the API Signing Key was generated, which should be stored within \.oci\config
config = oci.config.from_file()
Once you’ve done this, you can then use the Python SDK as you wish, in the example below I list all of the compute instances within my root compartment.
# Create client with the default config file (\.oci\config)
computeclient = oci.core.ComputeClient(config)
# List all of the compute instances within the compartment
list_instances_response = computeclient.list_instances(
compartment_id="Replace with the OCID of the compartment to query")
# List all of the compute instances
print(list_instances_response.data)
Here is the script in action, it lists (a lot of) data about each of the instances found within the compartment queried.
I have a Windows VM hosted within Oracle Cloud Infrastructure (OCI) that I needed to access remotely via RDP. The VM itself is hosted within a private subnet (therefore it doesn’t have a public IP address) so I’m not able to connect to it directly over the Internet ๐.
I could have used a “jump box” to access this, which would be another VM in a public subnet within the same VCN, essentially RDP’ing into this publicly available VM and then “jumping across” to the VM in the private subnet.
I decided to take a look at OCI Bastion instead, which provides the ability to connect to hosts that aren’t available externally, similar to Azure Bastion.
I followed the steps here to create a Bastion. Once I’d done this, I did the following to get access to my Windows VM via RDP.
Step 1 – Enable inbound RDP (port 3389) to the private subnet where the Windows VM is located from the IP address of the Bastion.
This needs to be done so that the Bastion deployed within OCI is able to connect to the VM via RDP (port 3389).
I created an ingress rule for RDP in the Security List associated with the subnet that contains the Windows VM via Networking > Virtual cloud network > Virtual Cloud Network Details > Security Lists. Here I specified the private endpoint IP address of the Bastion host (which can be found on the details page of the Bastion).
Step 2 – Create Bastion session
The next step is to create a Bastion session, it’s worth noting that by default these are valid for 3 hours and are then removed, although you can provision a session for a longer duration if needed.
To do this, click Create session within the Bastion.
Select SSH port forwarding session for Session type
Give the session a name, in this case I called the session WindowsVM
For Connect to the target host by using I chose Instance name and then selected the VM from the dropdown list, my instance is named WindowsServerPrivate – you could connect via the private IP address or hostname if you wish though.
Enter 3389 as the port
I then selected Generate SSH key pair and saved both the private and public key locally – if you already have a SSH key pair you use for OCI, you can select Choose SSH key file instead. The keys are used to authenticate to the Bastion when the SSH tunnel is created (more on this in step 3).
When this has been done click Create session, when the session state has changed to Active you can move on to the next step.
Step 3 – Create the SSH tunnel
In this step we’ll create an SSH tunnel between the local machine and the OCI Bastion, to do this select the session that has just been created and from the drop down choose Copy SSH command.
Paste this into a text editor, which should look something like this:
This is using the private key located at D:\OCI\OCI.key to connect to the Bastion, port 3390 on the localhost is redirected to port 3389 on 10.0.2.239 (which is the IP address of the Windows VM).
Run this command within Windows Terminal to create the SSH tunnel, here is an example of the command:
Step 4 – Connect via RDP
Finally we can connect via RDP! This may seem like a lot of work so far, but it literally takes 2 minutes once you have the Bastion setup – in a production/enterprise environment you’d probably script all of this up rather than clicking around the OCI portal ๐.
Launch the RDP client and connect to 127.0.0.1:3390, this should route the traffic via the SSH tunnel to the Bastion, which in turn will route it to the Windows VM.
There we go, we are connected:
Just remember, that by default the session will only live for 3 hours at which point you’ll need to go back through Steps 2-4 to re-connect.
Over the years I’ve created a few web apps in Python using the Flask Framework, a good (or not so good) example can be found here.
I typically host these within an Azure Web App, recently I was experimenting with container instances within OCI (Oracle Cloud Infrastructure), I set about attempting to port one of my web apps to run within a container, I ran into a small issue that I thought I’d document here (mainly for my future self).
When deploying a Python web app to an Azure Web App that uses Flask, I use the following code to run the web app (at the bottom of the Pythion application.py file – which contains the code for the Flask web app):
if __name__ == "__main__":
app.run()
This works like a charm locally (running the web app on http://localhost:5000) and when published to an Azure Web App, this runs on port 443 (https), for example https://(webappname).azurewebsites.net.
When running this app within a container, it failed miserably and the site didn’t render โน๏ธ. Looking at the logs within OCI this was because the container was listening on port 5000 (as it would typically do when running locally).
It turned out that I needed to update the application.py file and configure the port to listen on and override the default of port 5000 (as below – I used port 80/http to keep this simple).
if __name__ == "__main__":
app.run(port=80,host='0.0.0.0')
This did the trick and my web app worked correctly! It looks like Azure (Gunicorn) does some magic under the hood and override’s the default behaviour of listening on port 5000.
Whilst I ran into this issue using OCI, it would be the same if I was running Docker locally, Azure, GCP or AWS – it was an issue with me, rather than OCI ๐คฆโโ๏ธ.
I’ve recently stepped out of my Microsoft comfort zone and have been experimenting with AWS, GCP and OCI. One of my favourite features of Azure is Azure Functions.
I wrote an Azure Function during the pandemic as I needed a way to automagically generate a workout routine, as I could no longer attend my favourite circuit class – the code for this can be found here ๐๏ธโโ๏ธ.
This is a HTTP triggered Azure Function App that generates a list of exercises for a workout (from a pool of 26 different exercises), pass the query string exercices=(number) to the Function App URL to specify how many exercises you’d like including in the workout and the function app will work it’s magic ๐ช.
As this is fairly simple, I thought I’d have a go at adapting this to run as a function within OCI. I put together a short video that walks through the process of creating a function app in OCI, deploying the code and then finally testing the function app, the walkthrough video can be found below, the Python code used can be found here.
I was pleasantly surprised at how straightforward this was, despite a few small hiccups I managed to get this all done in less than a couple of hours โฑ๏ธ.
Some time ago I shared a Python script that I’d written that could complete the first level of Super Mario Land ๐ฎ.
Since then I’ve being thinking of other games that I could try to automate playthrough’s of. One game that I’ve never played (until recently) is Typing of the Dead Overkill, which is basically House of the Dead but instead of shooting enemies manually, you type a word to kill them, improving your typing skills whilst playing – who needs Mavis Beacon โจ๏ธ!
As this is fairly simple in nature it made me think that I could probably do something as follows to automate playing the game (using Python of course).
I grabbed a copy of the game from Steam and set about putting my master plan into action!
I managed to create a proof of concept for this (script available here), however it simply wasn’t performant enough so I kept dying โน๏ธ.
I think moving the OCR processing from Azure to my local device would make this a workable solution and is definitely something I’ll look at in the future when I have time.
Before I threw in the towel however, I thought I’d try a low-tech approach, which to many astonishment worked really well and effectively can complete the game without any manual user input!
This approach does the following……runs a loop that types each character on the keyboard (a-z) in order and then repeats, it’s not pretty but it does the job! I put in a 10 second pause so that the script can be launched before the game (I have two monitors and had the script running on my second monitor).
import pyautogui
import time
time.sleep(10)
i = 1
while i == 1:
pyautogui.write("qazwsxedcrfvtgbyhnujmikopl")
I recently needed to export a list of all Power Automate Flows from a tenant, along with details of any connectors they were using to read/write data such as SharePoint Online, Dataverse and Outlook. I needed this for preparation for a tenant-to-tenant migration to aid with planning ๐.
I put together the PowerShell script below, which outputs a list of all Flows along with their state (enabled or disabled) and the connectors that they use to a CSV file.
This script requires the Power Platform Administrators PowerShell module to be installed, instructions on how to install this can be found here. Simply update the $FilePath variable (which sets the location to write the CSV file to) and then you are good to go๐.
The challenge I had, was that the PowerShell script provided requires end-user interaction and I needed to run this script un-attended via a Group Policy Object (GPO). I managed to do this and below is my updated version of the script which writes the hardware hash to a local file on the device, in my final solution, this will write the file to a fileshare.
Another example of a complete edge case scenario, with little use to anyone – besides myself when I need to refer to this in the future, when I make the same mistake ๐.
I was recently playing around with Azure Migrate and performed a test migration of a VM from On-Premises (a local Hyper-V server in my lab) to Azure โ๏ธ.
I’d provisioned a new VM within my Hyper-V server On-Premises, configured this as a web server and then did a migration to Azure (which was a lot simpler than I thought!). The one thing I forgot to do was enable RDP on the On-Premises VM prior to migration, I’d been using Hyper-V Manager to remotely access the VM and configure it so completely forgot to do this ๐คฆโโ๏ธ.
The result of this, was that when the VM had been migrated to Azure it didn’t have RDP enabled so I was unable to access it, this is where the serial console came to the rescue and enabled me to configure RDP and get access to the migrated VM in Azure.
Here are the steps that I followed:
Step 1 – Enable the Serial Console
The first thing I needed to do was enable the serial console, the steps required are documented here.
You need to Run EnableEMS against the VM to do this:
Step 2 – Connect to the VM using the Serial Console
Select Serial Console within the Help section of the VM within Azure
Type cmd and press enter
Type ch -si 1 and press enter
Press any key
Input the credentials for the VM
If successfully authenticated, you should now have a command shell
Step 3 – Enable RDP and Create a Firewall Rule to allow access
Run the following commands within the command shell to enable RDP on the server and then configure Windows Firewall to allow inbound access.