leandrosardi / contabo-client

Ruby gem for performing some infrastructure operations on Contabo via API
MIT License
1 stars 0 forks source link

documentation #6

Closed leandrosardi closed 3 weeks ago

leandrosardi commented 3 weeks ago

GPT Prompt

I wrote a Ruby library for operating Contabo instances from code.

This library has 3 following features:

  1. instance creation
  2. instance cancelation
  3. instance reinstalling

I need your help with the documentation/

Here is the souce code of the library:

require 'net/http'
require 'json'
require 'uri'
require 'securerandom'

class ContaboClient
  def initialize(client_id:, client_secret:, api_user:, api_password:)
    @client_id = client_id
    @client_secret = client_secret
    @api_user = api_user
    @api_password = api_password
    @auth_url = 'https://auth.contabo.com/auth/realms/contabo/protocol/openid-connect/token'
    @api_url = 'https://api.contabo.com/v1/compute/instances'
  end

  def get_access_token
    uri = URI(@auth_url)
    response = Net::HTTP.post_form(uri, {
      'client_id' => @client_id,
      'client_secret' => @client_secret,
      'username' => @api_user,
      'password' => @api_password,
      'grant_type' => 'password'
    })
    JSON.parse(response.body)['access_token']
  end

  def create_secret(password, type = 'password')
    uri = URI('https://api.contabo.com/v1/secrets')

    request = Net::HTTP::Post.new(uri)
    request['Authorization'] = "Bearer #{access_token}"
    request['Content-Type'] = 'application/json'
    request['x-request-id'] = SecureRandom.uuid

    body = {
      name: "Ruby's Contabo Client #{SecureRandom.uuid}",
      value: password,
      type: type
    }

    request.body = body.to_json

    response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
      http.request(request)
    end

    data = JSON.parse(response.body)['data'].first
    raise "Failed to create secret. Error: #{response.body}" if data.nil?
    data['secretId']
  end

  def retrieve_images(page: 1, size: 10)
    uri = URI('https://api.contabo.com/v1/compute/images')
    params = { page: page, size: size }
    uri.query = URI.encode_www_form(params)

    request = Net::HTTP::Get.new(uri)
    request['Authorization'] = "Bearer #{access_token}"
    request['x-request-id'] = SecureRandom.uuid

    response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
      http.request(request)
    end

    JSON.parse(response.body)
  end

  def get_instances(page: 1, size: 10, order_by: nil, search: nil)
    uri = URI(@api_url)
    params = { page: page, size: size }
    params[:orderBy] = order_by if order_by
    params[:search] = search if search
    uri.query = URI.encode_www_form(params)

    request = Net::HTTP::Get.new(uri)
    request['Authorization'] = "Bearer #{access_token}"
    request['x-request-id'] = SecureRandom.uuid
    request['x-request-id'] = SecureRandom.uuid

    response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
      http.request(request)
    end

    JSON.parse(response.body)
  end

  def create_instance(image_id:, product_id:, region: 'EU', ssh_rsa:, root_password:, display_name:, user_data: '')
    uri = URI(@api_url)
    request = Net::HTTP::Post.new(uri)
    request['Authorization'] = "Bearer #{access_token}"
    request['Content-Type'] = 'application/json'
    request['x-request-id'] = SecureRandom.uuid

    root_password_secret_id = create_secret(root_password)
    root_ssh_secret_id = create_secret(ssh_rsa, 'ssh')

    body = {
      imageId: image_id,
      productId: product_id,
      region: region,
      sshKeys: [root_ssh_secret_id],
      rootPassword: root_password_secret_id,
      defaultUser: "root",
      displayName: display_name,
      userData: user_data
    }.compact

    request.body = body.to_json

    response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
      http.request(request)
    end

    JSON.parse(response.body)
  end

  def reinstall_instance(instance_id:, image_id:, root_password:, cloud_init: nil, user_data: '')
    uri = URI("https://api.contabo.com/v1/compute/instances/#{instance_id}")
    request = Net::HTTP::Put.new(uri)
    request['Authorization'] = "Bearer #{access_token}"
    request['Content-Type'] = 'application/json'
    request['x-request-id'] = SecureRandom.uuid
    root_password_secret_id = create_secret(root_password)

    body = {
      imageId: image_id,
      rootPassword: root_password_secret_id,
      defaultUser: "root",
      userData: user_data
    }.compact

    request.body = body.to_json

    response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
      http.request(request)
    end

    JSON.parse(response.body)
  end

  def access_token
    @access_token ||= get_access_token
  end

  def cancel_instance(instance_id:)
    uri = URI("#{@api_url}/#{instance_id}/cancel")
    request = Net::HTTP::Post.new(uri)
    request['Authorization'] = "Bearer #{access_token}"
    request['x-request-id'] = SecureRandom.uuid

    response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
      http.request(request)
    end

    JSON.parse(response.body)
  rescue StandardError => e
    puts "Error canceling instance: #{e.message}"
    nil
  end
end

Here is the source code of the creation example:

require_relative '../lib/contabo-client'  
require_relative './config.rb'  
require 'blackstack-core'
require 'colorize'
require 'json'  
require 'pry'  

# Initialize Contabo client  
client = ContaboClient.new(  
  client_id: CLIENT_ID,  
  client_secret: CLIENT_SECRET,  
  api_user: API_USER,  
  api_password: API_PASSWORD  
)  

# Retrieve images with error handling  
begin  
  # Fetch initial set of images  
  ret = client.retrieve_images(size: 100)  

  raise "Unexpected response format or empty response" unless ret && ret['_pagination'] && ret['data']  

  # Find the image ID for Ubuntu 20.04  
  image = ret['data'].find { |h| h['name'] == 'ubuntu-20.04' }  
  raise 'Image not found' if image.nil?  
  image_id = image['imageId']  

  # Set the root password directly here  
  root_password = 'HGT121124588ABC'
  # use the following command to generate ssh key
  # ssh-keygen -t ed25519 -b 4096 -C "your_email_here" -f "key_name_here"
  ssh_rsa = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMPfaX2P18lDbtoZsGC6fcqw7zoAbbNyGlrUI004QCe7 schaudhry722@gmail.com"

  user_data_script = <<~USER_DATA
    #cloud-config
    disable_cloud_init: true
    runcmd:
      - touch /etc/cloud/cloud-init.disabled
      - systemctl stop cloud-init
      - systemctl disable cloud-init
  USER_DATA
  # Create the instance with the retrieved image ID  
  instance = client.create_instance(  
    image_id: image_id,  
    product_id: 'V45',  
    region: 'EU',
    ssh_rsa: ssh_rsa,
    root_password: root_password,  
    display_name: 'MyUbuntu20Instance-root-access-6',
    user_data: user_data_script  
  )  

  # Output the created instance details  
  # Commented out since we're using binding.pry for debugging  
  puts JSON.pretty_generate(instance)

rescue StandardError => e  
  STDERR.puts "An error occurred: #{e.to_console}".red  
end

Here is the source code of the cancellation example:

require_relative '../lib/contabo-client'
require_relative './config.rb'
require 'blackstack-core'
require 'colorize'
require 'json'

# Constants
Z = 100
IP = '62.84.178.201'

# Initialize Contabo client
client = ContaboClient.new(
  client_id: CLIENT_ID,
  client_secret: CLIENT_SECRET,
  api_user: API_USER,
  api_password: API_PASSWORD
)

begin
  # Retrieve instances
  instances = client.get_instances(size: Z)

  # Debug prints to inspect response
  #puts "Response from get_instances:"
  if instances.nil?
    puts "Received nil response from get_instances."
    exit
  else
    puts JSON.pretty_generate(instances)
  end

  # Check for errors in the response
  if instances['error']
    puts "API returned an error: #{instances['error']}"
    exit
  end

  # Check if 'data' key is present and is an array
  if instances['data'].nil? || !instances['data'].is_a?(Array)
    puts "No instances returned or unexpected data format. Response: #{instances.inspect}"
    exit
  end

  unless instances['data'].any?
    puts "No instances found"
    exit
  end

  # Find the instance ID by IP
  instance = instances['data'].find do |h|
    ip_config_v4 = h.dig('ipConfig', 'v4')

    ip_config_v4.is_a?(Hash) && ip_config_v4['ip'] == IP
  end

  if instance.nil?
    puts "Instance with IP #{IP} not found in the retrieved instances."
    exit
  end

  # Use the correct instance ID for cancellation
  instance_id = instance['instanceId']

  # Debug: Print the instance details
  puts "Found instance with IP #{IP}"
  #puts JSON.pretty_generate(instance)

  # Request cancellation
  response = client.cancel_instance(instance_id: instance_id)

  # Print the response
  puts "Cancellation response:"
  if response.nil?
    puts "Received nil response from cancel_instance."
  else
    puts JSON.pretty_generate(response)
  end

rescue StandardError => e
  STDERR.puts "An error occurred: #{e.to_console}".red  
end

Here is the source code of the reinstallation example:

require_relative '../lib/contabo-client'
require_relative './config.rb'
require 'blackstack-core'
require 'colorize'
require 'json'

# Constants
Z = 100
IP = '62.84.178.201'

# Initialize Contabo client
client = ContaboClient.new(
  client_id: CLIENT_ID,
  client_secret: CLIENT_SECRET,
  api_user: API_USER,
  api_password: API_PASSWORD
)

begin
  # Retrieve instances
  instances = client.get_instances(size: Z)

  # Debug prints to inspect response
  #puts "Response from get_instances:"
  if instances.nil?
    puts "Received nil response from get_instances."
    exit
  else
    puts JSON.pretty_generate(instances)
  end

  # Check for errors in the response
  if instances['error']
    puts "API returned an error: #{instances['error']}"
    exit
  end

  # Check if 'data' key is present and is an array
  if instances['data'].nil? || !instances['data'].is_a?(Array)
    puts "No instances returned or unexpected data format. Response: #{instances.inspect}"
    exit
  end

  unless instances['data'].any?
    puts "No instances found"
    exit
  end

  # Find the instance ID by IP
  instance = instances['data'].find do |h|
    ip_config_v4 = h.dig('ipConfig', 'v4')

    ip_config_v4.is_a?(Hash) && ip_config_v4['ip'] == IP
  end

  if instance.nil?
    puts "Instance with IP #{IP} not found in the retrieved instances."
    exit
  end

  # Use the correct instance ID for cancellation
  instance_id = instance['instanceId']

  # Debug: Print the instance details
  puts "Found instance with IP #{IP}"
  #puts JSON.pretty_generate(instance)

  # Request cancellation
  response = client.cancel_instance(instance_id: instance_id)

  # Print the response
  puts "Cancellation response:"
  if response.nil?
    puts "Received nil response from cancel_instance."
  else
    puts JSON.pretty_generate(response)
  end

rescue StandardError => e
  STDERR.puts "An error occurred: #{e.to_console}".red  
end

Please write the README.md file.

leandrosardi commented 3 weeks ago

Done.