diff --git a/.gitignore b/.gitignore index 5e1422c..a8b01e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.sw* *.gem *.rbc /.config @@ -48,3 +49,4 @@ build-iPhoneSimulator/ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc +vendor/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b6430fc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM ruby:2.3-alpine + +ARG APP_DIR=/webapp + +RUN mkdir -p ${APP_DIR}/vendor + +WORKDIR ${APP_DIR} + +ADD vendor/ ${APP_DIR}/vendor/ + +ENV GEM_HOME=${APP_DIR}/vendor/gems +ENV BUNDLE_PATH=${APP_DIR}/vendor/gems +ENV BUNDLE_APP_CONFIG=${APP_DIR}/vendor/gems/.bundle +ENV BUNDLE_DISABLE_SHARED_GEMS=true + +ADD Gemfile* ${APP_DIR}/ +ADD app.rb ${APP_DIR}/ +ADD config.ru ${APP_DIR}/ +ADD views ${APP_DIR}/views/ +ADD assets ${APP_DIR}/assets/ +CMD bundle exec rackup -o 0.0.0.0 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..d15ad67 --- /dev/null +++ b/Gemfile @@ -0,0 +1,10 @@ +source 'https://rubygems.org' + +gem 'haml' +gem 'sinatra' +gem 'sentry-raven' +gem 'sentry-api' + +group :test do + gem 'rspec' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..11af685 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,59 @@ +GEM + remote: https://rubygems.org/ + specs: + diff-lcs (1.3) + faraday (0.13.1) + multipart-post (>= 1.2, < 3) + haml (5.0.2) + temple (>= 0.8.0) + tilt + httmultiparty (0.3.16) + httparty (>= 0.7.3) + mimemagic + multipart-post + httparty (0.15.6) + multi_xml (>= 0.5.2) + mimemagic (0.3.2) + multi_xml (0.6.0) + multipart-post (2.0.0) + mustermann (1.0.1) + rack (2.0.3) + rack-protection (2.0.0) + rack + rspec (3.6.0) + rspec-core (~> 3.6.0) + rspec-expectations (~> 3.6.0) + rspec-mocks (~> 3.6.0) + rspec-core (3.6.0) + rspec-support (~> 3.6.0) + rspec-expectations (3.6.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.6.0) + rspec-mocks (3.6.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.6.0) + rspec-support (3.6.0) + sentry-api (0.3.3) + httmultiparty (~> 0.3.16) + sentry-raven (2.6.3) + faraday (>= 0.7.6, < 1.0) + sinatra (2.0.0) + mustermann (~> 1.0) + rack (~> 2.0) + rack-protection (= 2.0.0) + tilt (~> 2.0) + temple (0.8.0) + tilt (2.0.8) + +PLATFORMS + ruby + +DEPENDENCIES + haml + rspec + sentry-api + sentry-raven + sinatra + +BUNDLED WITH + 1.15.3 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..40e461a --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ + +all: check container + +check: depends spec + +spec: + ./scripts/ruby bundle exec rspec -c + +depends: Gemfile + ./scripts/ruby bundle install + +run: depends + ./scripts/ruby bundle exec rackup -o 0.0.0.0 + +container: depends Dockerfile + docker build -t rtyler/codevalet-canary . + +clean: + rm -rf vendor + +.PHONY: all depends clean run check container spec diff --git a/README.adoc b/README.adoc index 7ec9bb2..f790856 100644 --- a/README.adoc +++ b/README.adoc @@ -2,7 +2,7 @@ image::https://codevalet.io/u/codevalet/job/CodeValet/job/canary/job/master/badge/icon[Build Status, link=http://codevalet.codevalet.io/blue/organizations/jenkins/CodeValet%2Fcanary/activit://codevalet.io/u/codevalet/blue/organizations/jenkins/CodeValet%2Fcanary/activity] -image::https://github.com/codevalet/canary/raw/master/assets/songbird-128.png[Grace] +image::https://github.com/codevalet/canary/raw/master/assets/images/songbird-128.png[Grace] Canary is the service providing public status and information for link:http://codevalet.io[Code Valet]. diff --git a/app.rb b/app.rb new file mode 100644 index 0000000..7bf7dbd --- /dev/null +++ b/app.rb @@ -0,0 +1,57 @@ +#!/usr/bin/env ruby + +require 'haml' +require 'raven' +require 'sinatra/base' +require 'sentry-api' + +module CodeValet + module Canary + class App < Sinatra::Base + enable :sessions + enable :raise_errors + disable :show_exceptions + + set :public_folder, File.dirname(__FILE__) + '/assets' + + SentryApi.configure do |config| + config.endpoint = 'https://app.getsentry.com/api/0' + config.auth_token = ENV['SENTRY_API_TOKEN'] + config.default_org_slug = 'codevalet' + end + + get '/' do + projects = [] + begin + projects = SentryApi.projects + rescue StandardError => exc + Raven.capture_exception(exc) + end + + haml :index, + :layout => :_base, + :locals => { + :projects => projects, + } + end + + get '/issue/:id' do + issue = nil + events = [] + begin + issue = SentryApi.issue(params['id']) + events = SentryApi.issue_events(params['id']) + rescue StandardError => exc + Raven.capture_exception(exc) + end + + haml :issue, + :layout => :_base, + :locals => { + :issue => issue, + :events => events, + } + end + end + end +end diff --git a/assets/images/create-new-pipeline.png b/assets/images/create-new-pipeline.png new file mode 100644 index 0000000..d30a913 Binary files /dev/null and b/assets/images/create-new-pipeline.png differ diff --git a/assets/images/favicon.ico b/assets/images/favicon.ico new file mode 100644 index 0000000..90a7fb2 Binary files /dev/null and b/assets/images/favicon.ico differ diff --git a/assets/images/github-32.png b/assets/images/github-32.png new file mode 100644 index 0000000..8b25551 Binary files /dev/null and b/assets/images/github-32.png differ diff --git a/assets/images/monkey-128.png b/assets/images/monkey-128.png new file mode 100644 index 0000000..90a7fb2 Binary files /dev/null and b/assets/images/monkey-128.png differ diff --git a/assets/songbird-128.png b/assets/images/songbird-128.png similarity index 100% rename from assets/songbird-128.png rename to assets/images/songbird-128.png diff --git a/assets/songbird-600.png b/assets/images/songbird-600.png similarity index 100% rename from assets/songbird-600.png rename to assets/images/songbird-600.png diff --git a/assets/songbird.svg b/assets/images/songbird.svg similarity index 100% rename from assets/songbird.svg rename to assets/images/songbird.svg diff --git a/assets/images/twitter-32.png b/assets/images/twitter-32.png new file mode 100644 index 0000000..5a6be0f Binary files /dev/null and b/assets/images/twitter-32.png differ diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..011e77f --- /dev/null +++ b/config.ru @@ -0,0 +1,12 @@ + +$LOAD_PATH << File.dirname(__FILE__) + +ENV['RACK_ENV'] ||= 'development' + +require 'app' +require 'raven' + +use Rack::Static, :urls => ["/css", "/img", "/js"], :root => "public" +use Raven::Rack + +run CodeValet::Canary::App diff --git a/scripts/ruby b/scripts/ruby new file mode 100755 index 0000000..b35cd63 --- /dev/null +++ b/scripts/ruby @@ -0,0 +1,14 @@ +#!/bin/sh + +exec docker run --rm -ti \ + -u $(id -u):$(id -g) \ + -w ${PWD} \ + -v ${PWD}:${PWD} \ + -e LANG=C.UTF-8 \ + -e GEM_HOME=${PWD}/vendor/gems \ + -e BUNDLE_PATH=${PWD}/vendor/gems \ + -e BUNDLE_APP_CONFIG=${PWD}/vendor/gems/.bundle \ + -e BUNDLE_DISABLE_SHARED_GEMS=true \ + -e SENTRY_API_TOKEN=$SENTRY_API_TOKEN \ + -p 9292:9292 \ + ruby:2.3 $@ diff --git a/views/_base.haml b/views/_base.haml new file mode 100644 index 0000000..33f29ce --- /dev/null +++ b/views/_base.haml @@ -0,0 +1,69 @@ +!!! XML +!!! +%html + %head + %meta{:name => 'referrer', :content => 'no-referrer'}/ + %link{:rel => 'stylesheet', + :href => 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css', + :integrity => 'sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ', + :crossorigin => 'anonymous'}/ + %script{:src => 'https://code.jquery.com/jquery-3.2.1.slim.min.js', + :integrity => 'sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN', + :crossorigin => 'anonymous'} + %script{:src => 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js', + :crossorigin => 'anonymous', + :integrity => 'sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn'} + %meta{:charset => 'utf-8'}/ + %meta{:name => 'viewport', + :content => 'width=device-width, initial-scale=1, shrink-to-fit=no'}/ + %link{:rel => 'icon', :type => 'image/ico', :href => '/images/favicon.ico'}/ + %title + Code Valet - Canary + %body + %nav.navbar.navbar-inverse.bg-warning.navbar-toggleable + %a.navbar-brand{:href => '/'} + %img{:src => '/images/songbird-128.png', + :width => '50', :height => '50', :alt => 'Grace the Canary in the Code Mine'}/ + Canary + .navbar-collapse + .navbar-nav + %a.nav-item.nav-link{:href => 'https://codevalet.io/'} + Return to Code Valet + + .container.mt-5 + = yield + + %hr/ + + .container + .row + .col-md-6 + %p + © 2017 + %a{:href => 'https://github.com/rtyler'} + R. Tyler Croy. + .col-md-6 + %p.text-right + %a{:href => 'https://twitter.com/codevalet'} + %img{:src => '/images/twitter-32.png', + :alt => 'Code Valet on Twitter', + :title => 'Code Valet on Twitter'}/ + %a{:href => 'https://github.com/codevalet'} + %img{:src => '/images/github-32.png', + :alt => 'Code Valet on GitHub', + :title => 'Code Valet on GitHub'}/ + .row + %p + Jenkins® is a registered trademark of + %a{:href => 'http://spi-inc.org'} + Software in the Public Interest, Inc. + + :javascript + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); + + ga('create', 'UA-104976589-1', 'auto'); + ga('send', 'pageview'); + diff --git a/views/index.haml b/views/index.haml new file mode 100644 index 0000000..7bc93e2 --- /dev/null +++ b/views/index.haml @@ -0,0 +1,46 @@ +.container + .row + .col-md-12 + %h2 + Meet Canary + %img{:src => '/images/songbird-128.png', + :title => 'Grace the Canary in the Code Mine', + :align => :right} + %p + Canary is part of + %a{:href => 'https://codevalet.io/', :target => '_blank'} + Code Valet + which provides radically transparent feedback for the service's + operation. Canary allows developers to triage and understand issues + affecting Code Valet, or one of its components. + + - projects.auto_paginate do |project| + .row + .col-md-12 + %h3= project.name + %p + - events = SentryApi.project_issues(project.slug) + - unless events.size > 0 + No recent events (yay). + - else + - events.each do |event| + - badge = 'danger' + - if event.level != 'error' + - badge = event.level + .row + .col-lg-10 + %strong + = event.title + + .mb-2.text-muted + %span.badge{:class => "badge-#{badge}"} + = event.count + = event.culprit + + %p + %a{:href => "issue/#{event.id}"} + View + · + %a{:href => event.permalink, :target => '_blank'} + View in Sentry + %hr/ diff --git a/views/issue.haml b/views/issue.haml new file mode 100644 index 0000000..50ecad6 --- /dev/null +++ b/views/issue.haml @@ -0,0 +1,49 @@ +.container + .row + .col-md-12 + - unless events + Something went wrong when rendering this page :( + - else + %h2 + = issue.title + %h3.text-muted + = issue.culprit + .row + .col-md-12 + %small + There are + %strong + = events.size + events associated with this issue. + + - events.each do |event| + .row + .col-md-12 + - event.tags.each do |tag| + %span.badge.badge-info + #{tag['key']}:#{tag['value']} + - event.entries.each do |entry| + / #{entry.inspect} + - if entry['type'] == 'message' + %p + - if entry['data']['formatted'] + = entry['data']['formatted'] + - else + = entry['data']['message'] + + - if entry['type'] == 'exception' + - entry['data']['values'].each do |val| + %ul.list-unstyled + - val['stacktrace']['frames'].reverse_each do |frame| + %li + %div.m-1 + %strong + = frame['module'] + in + %strong + = frame['function'] + at line #{frame['lineNo']} of + %code + = frame['absPath'] + + - break