Skip to content

API Usage with Ruby

This guide provides practical examples for using the Nextcloud Operator API with Ruby and the kubeclient gem.

Setup

Install the Kubernetes Ruby Client

gem install kubeclient

Or add to your Gemfile:

gem 'kubeclient', '~> 4.11'

Client Setup

require 'kubeclient'
require 'json'

# Load kubeconfig from default location
config = Kubeclient::Config.read(ENV['KUBECONFIG'] || File.expand_path('~/.kube/config'))

# Create client for core API
@client = Kubeclient::Client.new(
  config.context.api_endpoint + '/api',
  config.context.api_version,
  ssl_options: config.context.ssl_options,
  auth_options: config.context.auth_options
)

# Create client for custom resources (Nextcloud)
@custom_client = Kubeclient::Client.new(
  config.context.api_endpoint + '/apis/k8s.bnerd.com',
  'v1alpha1',
  ssl_options: config.context.ssl_options,
  auth_options: config.context.auth_options
)

Authentication

Using Service Account Token (Applications)

API_SERVER = ENV['KUBERNETES_SERVICE_HOST'] || 'https://kubernetes.default.svc'
TOKEN = File.read('/var/run/secrets/kubernetes.io/serviceaccount/token')
CA_CERT = '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'

ssl_options = { ca_file: CA_CERT, verify_ssl: OpenSSL::SSL::VERIFY_PEER }
auth_options = { bearer_token: TOKEN }

@client = Kubeclient::Client.new(
  "#{API_SERVER}/api", 'v1',
  ssl_options: ssl_options, auth_options: auth_options
)

@custom_client = Kubeclient::Client.new(
  "#{API_SERVER}/apis/k8s.bnerd.com", 'v1alpha1',
  ssl_options: ssl_options, auth_options: auth_options
)

Nextcloud CRUD Operations

Create from Pool

nextcloud = Kubeclient::Resource.new(
  metadata: {
    name: 'tenant-a-cloud',
    namespace: 'tenant-a'
  },
  spec: {
    poolSelector: { matchLabels: { profile: 'basic' } },
    ingress: { host: 'nc-basic.example.com' }
  }
)

begin
  result = @custom_client.create_nextcloud(nextcloud)
  puts "Created Nextcloud: #{result.metadata.name}"
rescue Kubeclient::ResourceAlreadyExistsError
  puts "Nextcloud already exists"
rescue Kubeclient::HttpError => e
  puts "Error: #{e.message}"
end

Create with Custom Configuration

nextcloud = Kubeclient::Resource.new(
  metadata: { name: 'tenant-a-custom', namespace: 'tenant-a' },
  spec: {
    poolSelector: { matchLabels: { profile: 'basic' } },
    version: '29',
    ingress: { host: 'custom.tenant-a.example.com', tls: { enabled: true } },
    database: { managed: true, type: 'postgresql' },
    redis: { enabled: true }
  }
)

result = @custom_client.create_nextcloud(nextcloud)

List All in Namespace

nextclouds = @custom_client.get_nextclouds(namespace: 'tenant-a')

nextclouds.each do |nc|
  puts "#{nc.metadata.name} - Phase: #{nc.status&.phase} - URL: #{nc.status&.url}"
end

Get Specific Resource

begin
  nc = @custom_client.get_nextcloud('tenant-a-cloud', 'tenant-a')
  puts "Phase: #{nc.status&.phase}"
  puts "URL: #{nc.status&.url}"
  puts "Admin: #{nc.status&.admin&.username}"
rescue Kubeclient::ResourceNotFoundError
  puts "Not found"
end

Get Admin Credentials

nc = @custom_client.get_nextcloud('tenant-a-cloud', 'tenant-a')

if nc.status&.admin
  puts "URL: #{nc.status.url}"
  puts "Username: #{nc.status.admin.username}"
  puts "Password: #{nc.status.admin.password}"
end

Update

nc = @custom_client.get_nextcloud('tenant-a-cloud', 'tenant-a')
nc.spec.ingress.host = 'new-hostname.example.com'
@custom_client.update_nextcloud(nc)

Patch (Partial Update)

patch = { spec: { ingress: { host: 'patched.example.com' } } }

@custom_client.patch_nextcloud(
  'tenant-a-cloud', patch, 'tenant-a', 'merge-patch'
)

Delete

begin
  @custom_client.delete_nextcloud('tenant-a-cloud', 'tenant-a')
  puts "Deleted"
rescue Kubeclient::ResourceNotFoundError
  puts "Not found"
end

Status and Monitoring

Watch Resources

watcher = @custom_client.watch_nextclouds(namespace: 'tenant-a')

watcher.each do |notice|
  nc = notice.object
  case notice.type
  when 'ADDED'    then puts "ADDED: #{nc.metadata.name}"
  when 'MODIFIED' then puts "MODIFIED: #{nc.metadata.name} (#{nc.status&.phase})"
  when 'DELETED'  then puts "DELETED: #{nc.metadata.name}"
  end
end

Wait for Ready

def wait_for_ready(client, name, namespace, timeout: 300)
  start_time = Time.now

  loop do
    nc = client.get_nextcloud(name, namespace)
    phase = nc.status&.phase

    return nc if phase == 'Ready'

    elapsed = Time.now - start_time
    raise "Timeout (phase: #{phase})" if elapsed > timeout

    puts "  Phase: #{phase} (#{elapsed.round}s)"
    sleep 5
  end
end

nc = wait_for_ready(@custom_client, 'tenant-a-cloud', 'tenant-a')
puts "URL: #{nc.status.url}"

Complete Example: Tenant Onboarding

#!/usr/bin/env ruby

require 'kubeclient'
require 'json'

class NextcloudTenantProvisioner
  def initialize(config_file = nil)
    config = Kubeclient::Config.read(
      config_file || ENV['KUBECONFIG'] || File.expand_path('~/.kube/config')
    )

    @client = Kubeclient::Client.new(
      config.context.api_endpoint + '/api', 'v1',
      ssl_options: config.context.ssl_options,
      auth_options: config.context.auth_options
    )

    @custom_client = Kubeclient::Client.new(
      config.context.api_endpoint + '/apis/k8s.bnerd.com', 'v1alpha1',
      ssl_options: config.context.ssl_options,
      auth_options: config.context.auth_options
    )
  end

  def provision_tenant(tenant_name:, nextcloud_name:, domain:, pool_profile: 'basic')
    # Step 1: Create namespace
    ns = Kubeclient::Resource.new(
      metadata: { name: tenant_name, labels: { tenant: tenant_name } }
    )
    begin
      @client.create_namespace(ns)
    rescue Kubeclient::ResourceAlreadyExistsError
      # OK
    end

    # Step 2: Create Nextcloud
    nc = Kubeclient::Resource.new(
      metadata: { name: nextcloud_name, namespace: tenant_name },
      spec: {
        poolSelector: { matchLabels: { profile: pool_profile } },
        ingress: { host: domain, tls: { enabled: true } }
      }
    )
    begin
      @custom_client.create_nextcloud(nc)
    rescue Kubeclient::ResourceAlreadyExistsError
      # OK
    end

    # Step 3: Wait for ready
    start_time = Time.now
    loop do
      result = @custom_client.get_nextcloud(nextcloud_name, tenant_name)
      phase = result.status&.phase

      if phase == 'Ready'
        puts "Nextcloud is ready!"
        puts "  URL: #{result.status.url}"
        puts "  Admin: #{result.status.admin&.username}"
        return result
      end

      raise "Timeout" if Time.now - start_time > 300
      sleep 5
    end
  end
end

# Usage
if __FILE__ == $0
  tenant = ARGV[0] || 'tenant-a'
  name = ARGV[1] || 'main-cloud'
  domain = ARGV[2] || "#{tenant}.example.com"

  provisioner = NextcloudTenantProvisioner.new
  provisioner.provision_tenant(
    tenant_name: tenant,
    nextcloud_name: name,
    domain: domain
  )
end
# Run with defaults
ruby provision_tenant.rb

# Run with custom values
ruby provision_tenant.rb tenant-b main-cloud tenant-b.example.com

Common HTTP Status Codes

Code Meaning Ruby Exception
200 Success -
201 Created -
404 Not found Kubeclient::ResourceNotFoundError
409 Already exists Kubeclient::ResourceAlreadyExistsError
422 Invalid spec Kubeclient::HttpError
403 Forbidden Kubeclient::HttpError
401 Unauthorized Kubeclient::HttpError