From 7640f045f181b56460cbf350e7601296c5b77e8b Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Fri, 6 Jun 2025 18:50:32 +0200 Subject: [PATCH] initial --- Containerfile | 34 ++++++++ Gemfile | 9 +++ Gemfile.lock | 62 +++++++++++++++ README.md | 120 ++++++++++++++++++++++++++++ TLDR.md | 11 +++ app.rb | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 448 insertions(+) create mode 100644 Containerfile create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 README.md create mode 100644 TLDR.md create mode 100644 app.rb diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..9681f94 --- /dev/null +++ b/Containerfile @@ -0,0 +1,34 @@ +# Use official Ruby image +FROM ruby:3.2-slim + +# Set working directory +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +# Copy Gemfile and Gemfile.lock +COPY Gemfile* ./ + +# Install Ruby gems +RUN bundle install --without development + +# Copy application code +COPY . . + +# Create non-root user +RUN groupadd -r appuser && useradd -r -g appuser appuser +RUN chown -R appuser:appuser /app +USER appuser + +# Expose port +EXPOSE 4567 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:4567/health || exit 1 + +# Default command +CMD ["ruby", "app.rb"] diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..b1fe56f --- /dev/null +++ b/Gemfile @@ -0,0 +1,9 @@ +source 'https://rubygems.org' + +gem 'sinatra', '~> 3.0' +gem 'puma', '~> 6.0' +gem 'json', '~> 2.6' + +group :development do + gem 'rerun' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..5b52a12 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,62 @@ +GEM + remote: https://rubygems.org/ + specs: + base64 (0.3.0) + ffi (1.17.2) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86-linux-gnu) + ffi (1.17.2-x86-linux-musl) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + json (2.12.2) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mustermann (3.0.3) + ruby2_keywords (~> 0.0.1) + nio4r (2.7.4) + puma (6.6.0) + nio4r (~> 2.0) + rack (2.2.17) + rack-protection (3.2.0) + base64 (>= 0.1.0) + rack (~> 2.2, >= 2.2.4) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rerun (0.14.0) + listen (~> 3.0) + ruby2_keywords (0.0.5) + sinatra (3.2.0) + mustermann (~> 3.0) + rack (~> 2.2, >= 2.2.4) + rack-protection (= 3.2.0) + tilt (~> 2.0) + tilt (2.6.0) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + ruby + x86-linux-gnu + x86-linux-musl + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + json (~> 2.6) + puma (~> 6.0) + rerun + sinatra (~> 3.0) + +BUNDLED WITH + 2.6.7 diff --git a/README.md b/README.md new file mode 100644 index 0000000..43cf30a --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# No as a Service (NaaS) + +A simple Sinatra-based web service that provides creative "no" responses in both HTML and JSON formats. + +## Features + +- **Dual Format Support**: Serves both HTML (for browsers) and JSON (for APIs) +- **Random Responses**: Returns a random "no" response from a curated list +- **Modern Styling**: Beautiful glassmorphism design for the HTML interface +- **Health Checks**: Built-in health check endpoint for monitoring +- **Containerized**: Ready for deployment with Podman/Docker + +## API Endpoints + +- `GET /` - Returns a random "no" (HTML by default, JSON with Accept header) +- `GET /api/no` - Explicit JSON endpoint with additional metadata +- `GET /health` - Health check endpoint +- `GET /*` - Catch-all that returns "no" for any other path + +## Content Negotiation + +The root endpoint (`/`) supports content negotiation: +- Browser requests get HTML +- Requests with `Accept: application/json` get JSON + +## Usage Examples + +### HTML (Browser) +``` +curl http://localhost:4567/ +``` + +### JSON +```bash +# Using Accept header +curl -H "Accept: application/json" http://localhost:4567/ + +# Using explicit API endpoint +curl http://localhost:4567/api/no +``` + +### Sample JSON Response +```json +{ + "answer": "Absolutely not", + "timestamp": "2025-06-06T12:00:00Z", + "service": "No as a Service", + "version": "1.0.0" +} +``` + +## Local Development + +### Prerequisites +- Ruby 3.2+ +- Bundler + +### Setup +```bash +# Install dependencies +bundle install + +# Run the application +ruby app.rb + +# Or use rerun for auto-reload during development +bundle exec rerun ruby app.rb +``` + +The application will be available at `http://localhost:4567` + +## Container Deployment + +### Build with Podman +```bash +podman build -t naas . +``` + +### Run with Podman +```bash +# Basic run +podman run -p 4567:4567 naas + +# Run in background with restart policy +podman run -d --name naas-service -p 4567:4567 --restart=always naas + +# Run with custom port +podman run -p 8080:4567 -e PORT=4567 naas +``` + +### Docker Commands +The same commands work with Docker by replacing `podman` with `docker`. + +## Environment Variables + +- `PORT` - Port to bind to (default: 4567) + +## Health Monitoring + +The service includes a health check endpoint at `/health` that returns: +```json +{ + "status": "healthy", + "timestamp": "2025-06-06T12:00:00Z" +} +``` + +## File Structure + +``` +. +├── app.rb # Main Sinatra application +├── Gemfile # Ruby dependencies +├── Containerfile # Container build instructions +└── README.md # This file +``` + +## License + +This project is released under the MIT License. Feel free to use it for your negative response needs! diff --git a/TLDR.md b/TLDR.md new file mode 100644 index 0000000..6d8bf75 --- /dev/null +++ b/TLDR.md @@ -0,0 +1,11 @@ +# Create project directory +mkdir no-as-a-service +cd no-as-a-service + +# Create the files above, then: +bundle install +ruby app.rb + +# Or build container: +podman build -t naas . +podman run -p 4567:4567 naas diff --git a/app.rb b/app.rb new file mode 100644 index 0000000..cb17f1a --- /dev/null +++ b/app.rb @@ -0,0 +1,212 @@ +require 'sinatra' +require 'json' + +# Configure Sinatra +set :port, ENV['PORT'] || 4567 +set :bind, '0.0.0.0' + +# Array of creative "no" responses +NO_RESPONSES = [ + "No", + "Nope", + "Absolutely not", + "Not a chance", + "Never", + "No way", + "Negative", + "Not happening", + "Nah", + "No siree", + "Not today", + "Dream on", + "Over my dead body", + "When pigs fly", + "Not in a million years", + "Forget about it", + "No dice", + "Not on your life", + "Fat chance", + "No can do" +].freeze + +# Helper method to get a random "no" +def get_no_response + NO_RESPONSES.sample +end + +# Root endpoint - returns HTML by default +get '/' do + no_response = get_no_response + + case request.accept.first&.to_s + when /application\/json/ + content_type :json + { answer: no_response, timestamp: Time.now.iso8601 }.to_json + else + content_type :html + erb :index, locals: { no_response: no_response } + end +end + +# Explicit JSON endpoint +get '/api/no' do + content_type :json + { + answer: get_no_response, + timestamp: Time.now.iso8601, + service: "No as a Service", + version: "1.0.0" + }.to_json +end + +# Health check endpoint +get '/health' do + content_type :json + { status: "healthy", timestamp: Time.now.iso8601 }.to_json +end + +# Catch-all route for any other endpoint +get '/*' do + no_response = get_no_response + + case request.accept.first&.to_s + when /application\/json/ + content_type :json + { answer: no_response, timestamp: Time.now.iso8601 }.to_json + else + content_type :html + erb :index, locals: { no_response: no_response } + end +end + +__END__ + +@@index + + + + + + No as a Service + + + +
+

No as a Service

+

The definitive API for negative responses

+ +
+ <%= no_response %> +
+ + + +
+

API Usage

+

JSON API: GET /api/no

+

Accept JSON: curl -H "Accept: application/json" /

+

Health Check: GET /health

+
+
+ +