refactored the api (fixes #6)
parent
b7cc3f0083
commit
d12cdec3b8
5
Gemfile
5
Gemfile
|
@ -17,7 +17,8 @@ gem 'rake'
|
|||
# Component requirements
|
||||
gem 'slim'
|
||||
|
||||
gem 'sinatra'
|
||||
gem 'sinatra', '~> 1.4.5'
|
||||
gem 'rack-protection', '~> 1.5.3'
|
||||
|
||||
gem 'rack', '~> 1.6.0'
|
||||
|
||||
|
@ -28,3 +29,5 @@ gem 'pry'
|
|||
gem 'memoist'
|
||||
|
||||
gem "sinatra-cross_origin", "~> 0.3.1"
|
||||
|
||||
gem 'sinatra-session'
|
||||
|
|
|
@ -29,6 +29,8 @@ GEM
|
|||
rack-protection (~> 1.4)
|
||||
tilt (>= 1.3, < 3)
|
||||
sinatra-cross_origin (0.3.2)
|
||||
sinatra-session (1.0.0)
|
||||
sinatra (>= 1.0)
|
||||
slim (3.0.6)
|
||||
temple (~> 0.7.3)
|
||||
tilt (>= 1.3.3, < 2.1)
|
||||
|
@ -55,9 +57,11 @@ DEPENDENCIES
|
|||
oj
|
||||
pry
|
||||
rack (~> 1.6.0)
|
||||
rack-protection (~> 1.5.3)
|
||||
rake
|
||||
sinatra
|
||||
sinatra (~> 1.4.5)
|
||||
sinatra-cross_origin (~> 0.3.1)
|
||||
sinatra-session
|
||||
slim
|
||||
thin
|
||||
unicorn
|
||||
|
@ -66,4 +70,4 @@ RUBY VERSION
|
|||
ruby 2.3.1p112
|
||||
|
||||
BUNDLED WITH
|
||||
1.13.7
|
||||
1.14.6
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
apiserver.rb
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import RepoBrowser from './components/RepoBrowser'
|
||||
import AppContainer from './views/AppContainer.jsx';
|
||||
|
||||
ReactDOM.render(
|
||||
<RepoBrowser />,
|
||||
<AppContainer/>,
|
||||
document.getElementById('app')
|
||||
);
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import React, { Component } from 'react';
|
||||
import { NavDropdown, NavItem, MenuItem } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default class Login extends Component {
|
||||
|
||||
displayLogin(){
|
||||
if (this.props.registry.username){
|
||||
return(
|
||||
<NavDropdown eventKey={this.props.eventKey} title={this.props.registry.username} id="login-dropdown">
|
||||
<MenuItem eventKey={this.props.eventKey + 0.1} href="/logout">Logout</MenuItem>
|
||||
</NavDropdown>
|
||||
)
|
||||
}
|
||||
return(
|
||||
<NavItem eventKey={this.props.eventKey}>Login</NavItem>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
this.displayLogin()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -78,8 +78,6 @@ class RepoBrowser extends React.Component {
|
|||
|
||||
render(){
|
||||
return(
|
||||
<div className="main-container">
|
||||
<Header />
|
||||
<div className="container">
|
||||
<div className="col-sm-3">
|
||||
<Repos repo={this.state.repo} setRepo={(name) => this.handleSetRepo(name)}/>
|
||||
|
@ -91,8 +89,6 @@ class RepoBrowser extends React.Component {
|
|||
<TagInfo tag={this.state.tag} repo={this.state.repo} handleTagDelete={(repo, tag) => this.handleTagDelete(repo, tag)} getinfo={this.state.getinfo} registry={this.state.registry}/>
|
||||
</div>
|
||||
</div>
|
||||
<Footer registry={this.state.registry} />
|
||||
</div>
|
||||
)};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Navbar } from 'react-bootstrap';
|
||||
|
||||
export default class Header extends React.Component {
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<Navbar staticTop={true}>
|
||||
<Navbar.Header>
|
||||
<img className='navbar-brand' src="mini-logo.svg"/>
|
||||
<Navbar.Brand>
|
||||
Crane Operator
|
||||
</Navbar.Brand>
|
||||
</Navbar.Header>
|
||||
</Navbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
import React from 'react';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Route,
|
||||
Link
|
||||
} from 'react-router-dom'
|
||||
import HomeView from './HomeView.jsx';
|
||||
import LoginView from './LoginView.jsx';
|
||||
import Header from './sections/Header.jsx';
|
||||
import Footer from './sections/Footer.jsx';
|
||||
|
||||
export default class AppContainer extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
registry: {}
|
||||
}
|
||||
}
|
||||
|
||||
fetchRegistryInfo(){
|
||||
return axios.get(`/api/registryinfo`)
|
||||
.then(function (response) {
|
||||
this.setState({
|
||||
registry: response.data
|
||||
})
|
||||
}.bind(this))
|
||||
.catch(function (response) {
|
||||
console.log('ERROR IN AXIOS! ' + response);
|
||||
});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchRegistryInfo()
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Router>
|
||||
<div>
|
||||
<Header registry={this.state.registry}/>
|
||||
|
||||
<div className="container">
|
||||
<Route exact path="/" component={HomeView}/>
|
||||
<Route exact path="/login" component={LoginView}/>
|
||||
</div>
|
||||
|
||||
<Footer registry={this.state.registry}/>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
export default class HomeView extends Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
loaded: false,
|
||||
term: ""
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Home</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import React, { Component } from 'react';
|
||||
import { FormControl, FormGroup, ControlLabel, Col, Row } from 'react-bootstrap';
|
||||
|
||||
export default class LoginView extends Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
username: undefined,
|
||||
password: undefined
|
||||
}
|
||||
}
|
||||
|
||||
handleUsername(event){
|
||||
this.setState({
|
||||
username: event.target.value
|
||||
})
|
||||
}
|
||||
|
||||
handlePassword(event){
|
||||
this.setState({
|
||||
password: event.target.value
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Login to Docker Registry</h1>
|
||||
<Row>
|
||||
<form>
|
||||
<Col md={4}>
|
||||
<Row style={{paddingBottom: "1em", paddingTop: "2em"}}>
|
||||
<Col sm={12}>
|
||||
<FormControl type="text"
|
||||
placeholder="Username"
|
||||
onChange={(event) => this.handleUsername(event)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col sm={12}>
|
||||
<FormControl type="password"
|
||||
placeholder="Password"
|
||||
onChange={(event) => this.handlePassword(event)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
</Col>
|
||||
</form>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -19,7 +19,3 @@ export default class Footer extends React.Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
Footer.propTypes = {
|
||||
registry: React.PropTypes.object.isRequired
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Navbar, Nav, NavItem } from 'react-bootstrap';
|
||||
import Login from '../../components/Login.jsx';
|
||||
|
||||
export default class Header extends React.Component {
|
||||
title(){
|
||||
if(this.props.registry.title){
|
||||
return(this.props.registry.title)
|
||||
}
|
||||
return("Crane Operator")
|
||||
};
|
||||
|
||||
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<Navbar staticTop={true}>
|
||||
<Navbar.Header>
|
||||
<img className='navbar-brand' src="mini-logo.svg"/>
|
||||
<Navbar.Brand>
|
||||
<Link to="/">{this.title()}</Link>
|
||||
</Navbar.Brand>
|
||||
</Navbar.Header>
|
||||
<Nav pullRight>
|
||||
<Login registry={this.props.registry} eventKey={1}/>
|
||||
</Nav>
|
||||
</Navbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
class Configuration
|
||||
|
||||
attr_accessor :registry_password,
|
||||
:registry_username,
|
||||
:registry_host,
|
||||
:registry_port,
|
||||
:registry_protocol,
|
||||
:ssl_verify,
|
||||
:registry_public_url,
|
||||
:delete_allowed,
|
||||
:username,
|
||||
:password,
|
||||
:version
|
||||
|
||||
def initialize
|
||||
@registry_username = ENV['REGISTRY_USERNAME']
|
||||
@registry_password = ENV['REGISTRY_PASSWORD']
|
||||
@registry_host = ENV['REGISTRY_HOST'] || 'localhost'
|
||||
@registry_port = ENV['REGISTRY_PORT'] || '5000'
|
||||
@registry_protocol = ENV['REGISTRY_PROTOCOL'] || 'https'
|
||||
@registry_public_url = ENV['REGISTRY_PUBLIC_URL'] || "#{@registry_host}:#{@registry_port}"
|
||||
@ssl_verify = to_bool(ENV['SSL_VERIFY'] || 'true')
|
||||
@delete_allowed = to_bool(ENV['REGISTRY_ALLOW_DELETE'] || 'false')
|
||||
@username = ENV['USERNAME']
|
||||
@password = ENV['PASSWORD']
|
||||
@version = "2.2"
|
||||
end
|
||||
|
||||
def to_bool(str)
|
||||
str.to_s.downcase == 'true'
|
||||
end
|
||||
|
||||
def registry_url
|
||||
"#{registry_protocol}://#{registry_host}:#{registry_port}"
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,55 @@
|
|||
require 'base64'
|
||||
require 'oj'
|
||||
require 'httparty'
|
||||
|
||||
module Helpers
|
||||
|
||||
def sort_versions(ary)
|
||||
valid_version_numbers = ary.select { |i| i if i.match(/^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+(-[[:alnum:]]+)?$/) }
|
||||
non_valid_version_numbers = ary - valid_version_numbers
|
||||
versions = valid_version_numbers.sort_by {|v| Gem::Version.new( v.gsub(/^[a-z|A-Z|.]*/, '') ) } + non_valid_version_numbers.sort
|
||||
if versions.include?('latest')
|
||||
# Make sure 'latest' appears at the top of the list
|
||||
versions.delete('latest')
|
||||
versions.push('latest')
|
||||
end
|
||||
versions
|
||||
end
|
||||
|
||||
def to_bool(str)
|
||||
str.to_s.downcase == 'true'
|
||||
end
|
||||
|
||||
def html(view)
|
||||
File.read(File.join('public', "#{view.to_s}.html"))
|
||||
end
|
||||
|
||||
def generateHeaders(config, session, headers={})
|
||||
username = session[:username] || config.registry_username
|
||||
password = session[:password] || config.registry_password
|
||||
if username
|
||||
headers['Authorization'] = "Basic #{base64_docker_auth(username, password)}"
|
||||
end
|
||||
end
|
||||
|
||||
def base64_docker_auth(username, password)
|
||||
Base64.encode64("#{username}:#{password}").chomp
|
||||
end
|
||||
|
||||
def append_header(headers, addl_header)
|
||||
headers.merge addl_header
|
||||
end
|
||||
|
||||
def get(url, config, session, headers={})
|
||||
response = HTTParty.get( "#{config.registry_url}#{url}", verify: config.ssl_verify, headers: generateHeaders(config, session, headers) )
|
||||
Oj.load response.body
|
||||
end
|
||||
|
||||
def get_head(url, config, session, headers={})
|
||||
HTTParty.head( "#{registry_url}#{url}", verify: config.ssl_verify, headers: generateHeaders(config, session, headers) )
|
||||
end
|
||||
|
||||
def send_delete(url, config, session, headers={})
|
||||
HTTParty.delete( "#{registry_url}#{url}", verify: config.ssl_verify, headers: generateHeaders(config, session, headers) )
|
||||
end
|
||||
end
|
26
package.json
26
package.json
|
@ -5,24 +5,24 @@
|
|||
"main": "bundle.js",
|
||||
"dependencies": {
|
||||
"axios": "^0.8.0",
|
||||
"history": "^1.13.1",
|
||||
"moment": "^2.13.0",
|
||||
"re-base": "^1.5.1",
|
||||
"react": "^0.14.3",
|
||||
"react-bootstrap": "^0.29.3",
|
||||
"react-datetime": "^2.1.0",
|
||||
"react-dom": "^0.14.3",
|
||||
"react-intl": "^2.1.2",
|
||||
"react-loader": "^2.4.0",
|
||||
"react-router": "^1.0.1",
|
||||
"react-time": "^4.0.3",
|
||||
"babel-core": "^6.3.13",
|
||||
"babel-loader": "^6.2.0",
|
||||
"babel-preset-es2015": "^6.3.13",
|
||||
"babel-preset-react": "^6.3.13",
|
||||
"webpack": "^1.13.0",
|
||||
"esprima-fb": "15001.1001.0-dev-harmony-fb",
|
||||
"history": "^1.13.1",
|
||||
"moment": "^2.13.0",
|
||||
"prop-types": "^15.5.8",
|
||||
"esprima-fb": "15001.1001.0-dev-harmony-fb"
|
||||
"re-base": "^1.5.1",
|
||||
"react": "^15.6.1",
|
||||
"react-datetime": "^2.1.0",
|
||||
"react-dom": "^15.6.1",
|
||||
"react-intl": "^2.1.2",
|
||||
"react-loader": "^2.4.0",
|
||||
"react-router": "^1.0.3",
|
||||
"react-router-dom": "^4.1.2",
|
||||
"react-time": "^4.0.3",
|
||||
"webpack": "^1.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
|
|
|
@ -11,23 +11,23 @@
|
|||
<link rel="stylesheet" href="/bootstrap-theme.css" crossorigin="anonymous">
|
||||
|
||||
<!-- font awesome -->
|
||||
<link rel="stylesheet" href="css/font-awesome.min.css" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="/css/font-awesome.min.css" crossorigin="anonymous">
|
||||
|
||||
<!-- application css -->
|
||||
<link rel="stylesheet" href="/app.css" crossorigin="anonymous">
|
||||
|
||||
<!-- jquery -->
|
||||
<script src="jquery-3.1.0.min.js"></script>
|
||||
<script src="/jquery-3.1.0.min.js"></script>
|
||||
|
||||
<!-- bootstrap js -->
|
||||
<script src="bootstrap.min.js"></script>
|
||||
<script src="/bootstrap.min.js"></script>
|
||||
|
||||
<!-- bootbox -->
|
||||
<script src="bootbox.min.js"></script>
|
||||
<script src="/bootbox.min.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="bundle.js"></script>
|
||||
<script src="/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
193
server.rb
193
server.rb
|
@ -1,17 +1,19 @@
|
|||
require 'oj'
|
||||
require 'httparty'
|
||||
require 'pry'
|
||||
require 'erb'
|
||||
require 'time'
|
||||
require 'sinatra/base'
|
||||
require 'sinatra/cross_origin'
|
||||
require 'base64'
|
||||
require './lib/config.rb'
|
||||
require './lib/helpers.rb'
|
||||
|
||||
class CraneOp < Sinatra::Base
|
||||
register Sinatra::CrossOrigin
|
||||
include Helpers
|
||||
|
||||
configure do
|
||||
enable :cross_origin
|
||||
enable :sessions
|
||||
mime_type :javascript, 'application/javascript'
|
||||
mime_type :javascript, 'text/javascript'
|
||||
set :logging, true
|
||||
|
@ -22,118 +24,43 @@ class CraneOp < Sinatra::Base
|
|||
set :max_age, "1728000"
|
||||
set :expose_headers, ['Content-Type']
|
||||
set :json_encoder, :to_json
|
||||
set :session_secret, (ENV["SESSION_SECRET"] || "insecure-session-secret!")
|
||||
end
|
||||
|
||||
## Setup ##
|
||||
|
||||
def registry_host
|
||||
ENV['REGISTRY_HOST'] || 'localhost'
|
||||
def conf
|
||||
return Configuration.new
|
||||
end
|
||||
|
||||
def registry_port
|
||||
ENV['REGISTRY_PORT'] || '5000'
|
||||
end
|
||||
## Basic Auth ##
|
||||
|
||||
def registry_proto
|
||||
ENV['REGISTRY_PROTO'] || 'https'
|
||||
end
|
||||
|
||||
def registry_ssl_verify
|
||||
ENV['REGISTRY_SSL_VERIFY'] || 'true'
|
||||
end
|
||||
|
||||
def registry_public_url
|
||||
ENV['REGISTRY_PUBLIC_URL'] || "#{registry_host}:#{registry_port}"
|
||||
end
|
||||
|
||||
def registry_username
|
||||
ENV['REGISTRY_USERNAME']
|
||||
end
|
||||
|
||||
def registry_password
|
||||
ENV['REGISTRY_PASSWORD']
|
||||
end
|
||||
|
||||
def delete_allowed
|
||||
ENV['REGISTRY_ALLOW_DELETE'] || 'false'
|
||||
end
|
||||
|
||||
## Authentication ##
|
||||
|
||||
if ENV['USERNAME']
|
||||
if Configuration.new.username
|
||||
config = Configuration.new
|
||||
use Rack::Auth::Basic, "Please Authenticate to View" do |username, password|
|
||||
username == ENV['USERNAME'] and password == ( ENV['PASSWORD'] || '' )
|
||||
username == config.username and password == ( config.password || '' )
|
||||
end
|
||||
end
|
||||
|
||||
def base64_docker_auth
|
||||
Base64.encode64("#{registry_username}:#{registry_password}").chomp
|
||||
end
|
||||
|
||||
def hdrs
|
||||
h = {}
|
||||
if registry_username
|
||||
h['Authorization'] = "Basic #{base64_docker_auth}"
|
||||
end
|
||||
return h
|
||||
end
|
||||
|
||||
def append_header(h, addl_header)
|
||||
h.merge addl_header
|
||||
end
|
||||
|
||||
## Helpers ##
|
||||
|
||||
def to_bool(str)
|
||||
str.to_s.downcase == 'true'
|
||||
end
|
||||
|
||||
def html(view)
|
||||
File.read(File.join('public', "#{view.to_s}.html"))
|
||||
end
|
||||
|
||||
def sort_versions(ary)
|
||||
valid_version_numbers = ary.select { |i| i if i.match(/^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+(-[[:alnum:]]+)?$/) }
|
||||
non_valid_version_numbers = ary - valid_version_numbers
|
||||
versions = valid_version_numbers.sort_by {|v| Gem::Version.new( v.gsub(/^[a-z|A-Z|.]*/, '') ) } + non_valid_version_numbers.sort
|
||||
if versions.include?('latest')
|
||||
# Make sure 'latest' appears at the top of the list
|
||||
versions.delete('latest')
|
||||
versions.push('latest')
|
||||
end
|
||||
versions
|
||||
end
|
||||
|
||||
def registry_url
|
||||
url_parts = []
|
||||
|
||||
url_parts << registry_proto
|
||||
url_parts << "://"
|
||||
url_parts << registry_host
|
||||
url_parts << ":"
|
||||
url_parts << registry_port
|
||||
|
||||
url_parts.join
|
||||
end
|
||||
|
||||
## Registry API Methods ##
|
||||
|
||||
def containers
|
||||
response = HTTParty.get( "#{registry_url}/v2/_catalog", verify: to_bool(registry_ssl_verify), headers: hdrs )
|
||||
json = Oj.load response.body
|
||||
def containers(filter=nil)
|
||||
json = get("/v2/_catalog", conf, session)
|
||||
if filter
|
||||
return json['repositories'].select{ |i| i.match(/#{filter}.*/)}
|
||||
end
|
||||
json['repositories']
|
||||
end
|
||||
|
||||
def container_tags(repo)
|
||||
response = HTTParty.get( "#{registry_url}/v2/#{repo}/tags/list", verify: to_bool(registry_ssl_verify), headers: hdrs )
|
||||
json = Oj.load response.body
|
||||
def container_tags(repo, filter=nil)
|
||||
json = get("/v2/#{repo}/tags/list", conf, session)
|
||||
tags = json['tags'] || []
|
||||
tags = sort_versions(tags).reverse
|
||||
if filter
|
||||
return sort_versions(tags.select{ |i| i.match(/#{filter}.*/)}).reverse
|
||||
end
|
||||
sort_versions(tags).reverse
|
||||
end
|
||||
|
||||
def container_info(repo, manifest)
|
||||
response = HTTParty.get( "#{registry_url}/v2/#{repo}/manifests/#{manifest}", verify: to_bool(registry_ssl_verify), headers: hdrs )
|
||||
json = Oj.load response.body
|
||||
json = get("/v2/#{repo}/manifests/#{manifest}", conf, session)
|
||||
|
||||
# Add extra fields for easy display
|
||||
json['information'] = Oj.load(json['history'].first['v1Compatibility'])
|
||||
|
@ -145,43 +72,50 @@ class CraneOp < Sinatra::Base
|
|||
end
|
||||
|
||||
def fetch_digest(repo, manifest)
|
||||
h = append_header(hdrs, { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json'})
|
||||
response = HTTParty.head( "#{registry_url}/v2/#{repo}/manifests/#{manifest}", verify: to_bool(registry_ssl_verify), headers: h )
|
||||
response = get_head("/v2/#{repo}/manifests/#{manifest}", conf, session, { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json'})
|
||||
return response.headers["docker-content-digest"]
|
||||
end
|
||||
|
||||
def image_delete(repo, manifest)
|
||||
h = append_header(hdrs, { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json'})
|
||||
digest = fetch_digest(repo, manifest)
|
||||
response = HTTParty.delete( "#{registry_url}/v2/#{repo}/manifests/#{digest}", verify: to_bool(registry_ssl_verify), headers: h )
|
||||
return response
|
||||
return send_delete("/v2/#{repo}/manifests/#{digest}", conf, session, { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json'})
|
||||
end
|
||||
|
||||
## Endpoints ##
|
||||
|
||||
get '/' do
|
||||
html :index
|
||||
get '/api' do
|
||||
return "API Version #{conf.version}"
|
||||
end
|
||||
|
||||
get '/containers.json' do
|
||||
get '/api/containers' do
|
||||
content_type :json
|
||||
|
||||
containers.to_json
|
||||
containers(params[:filter]).to_json
|
||||
end
|
||||
|
||||
get '/container/*/tags.json' do |container|
|
||||
get '/api/tags/*' do |container|
|
||||
content_type :json
|
||||
|
||||
tags = container_tags(container)
|
||||
tags = container_tags(container, params[:filter])
|
||||
halt 404 if tags.nil?
|
||||
tags.to_json
|
||||
end
|
||||
|
||||
get /container\/(.*\/)(.*.json)/ do |container, tag|
|
||||
post '/api/login' do
|
||||
content_type :json
|
||||
params = Oj.load(request.body.read)
|
||||
session[:username] = params['username']
|
||||
session[:password] = params['password']
|
||||
{status: "success"}.to_json
|
||||
end
|
||||
|
||||
get '/logout' do
|
||||
session.destroy
|
||||
redirect '/'
|
||||
end
|
||||
|
||||
get /api\/containers\/(.*\/)(.*)/ do |container, tag|
|
||||
|
||||
# This is here because we need to handle slashes in container names
|
||||
container.chop!
|
||||
tag.gsub!('.json', '')
|
||||
|
||||
content_type :json
|
||||
|
||||
|
@ -193,28 +127,43 @@ class CraneOp < Sinatra::Base
|
|||
info.to_json
|
||||
end
|
||||
|
||||
get '/registryinfo' do
|
||||
get '/api/registryinfo' do
|
||||
content_type :json
|
||||
{
|
||||
host: registry_host,
|
||||
public_url: registry_public_url,
|
||||
port: registry_port,
|
||||
protocol: registry_proto,
|
||||
ssl_verify: to_bool(registry_ssl_verify),
|
||||
delete_allowed: to_bool(delete_allowed),
|
||||
}.to_json
|
||||
info = {
|
||||
host: conf.registry_host,
|
||||
public_url: conf.registry_public_url,
|
||||
port: conf.registry_port,
|
||||
protocol: conf.registry_protocol,
|
||||
ssl_verify: conf.ssl_verify,
|
||||
delete_allowed: conf.delete_allowed,
|
||||
}
|
||||
if session[:username]
|
||||
info[:username] = session[:username]
|
||||
end
|
||||
info.to_json
|
||||
end
|
||||
|
||||
delete /container\/(.*\/)(.*.json)/ do |container, tag|
|
||||
delete /api\/containers\/(.*\/)(.*)/ do |container, tag|
|
||||
halt 404 unless to_bool(delete_allowed)
|
||||
|
||||
container.chop!
|
||||
tag.gsub!('.json', '')
|
||||
response = image_delete( container, tag )
|
||||
headers = response.headers
|
||||
response.body
|
||||
end
|
||||
|
||||
# send endpoints that the react app handles
|
||||
[
|
||||
'/',
|
||||
'/container',
|
||||
'/container/*',
|
||||
'/login',
|
||||
].each do |route|
|
||||
get route do
|
||||
html :index
|
||||
end
|
||||
end
|
||||
|
||||
# Error Handlers
|
||||
error do
|
||||
File.read(File.join('public', '500.html'))
|
||||
|
|
Loading…
Reference in New Issue