create dockerfile, create more configuration options
This commit is contained in:
parent
b9e41f6d6e
commit
0546b71c5e
|
@ -0,0 +1,6 @@
|
|||
/target
|
||||
.env
|
||||
.vscode
|
||||
test.db
|
||||
/registry
|
||||
config.toml
|
|
@ -1,6 +1,5 @@
|
|||
/target
|
||||
.env
|
||||
.vscode
|
||||
test.db
|
||||
/registry
|
||||
config.toml
|
|
@ -0,0 +1,45 @@
|
|||
FROM rust:alpine3.14 as builder
|
||||
|
||||
# update packages
|
||||
RUN apk update
|
||||
RUN apk add build-base openssl-dev ca-certificates
|
||||
|
||||
# create root application folder
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./ /app/src
|
||||
|
||||
# Install rust toolchains
|
||||
RUN rustup toolchain install stable
|
||||
RUN rustup default stable
|
||||
|
||||
WORKDIR /app/src
|
||||
|
||||
# Build dependencies only. Separate these for caches
|
||||
RUN cargo install cargo-build-deps
|
||||
RUN cargo build-deps --release
|
||||
|
||||
# Build the release executable.
|
||||
RUN cargo build --release
|
||||
|
||||
# Runner stage. I tried using distroless (gcr.io/distroless/static-debian11), but the image was only ~3MBs smaller than
|
||||
# alpine. I chose to use alpine since a user can easily be added to the image.
|
||||
FROM alpine:3.17
|
||||
|
||||
ARG UNAME=orca-registry
|
||||
ARG UID=1000
|
||||
ARG GID=1000
|
||||
|
||||
# Add user and copy the executable from the build stage.
|
||||
RUN adduser --disabled-password --gecos "" $UNAME -s -G $GID -u $UID
|
||||
COPY --from=builder --chown=$UID:$GID /app/src/target/release/orca-registry /app/orca-registry
|
||||
|
||||
RUN chown -R $UID:$GID /app
|
||||
|
||||
USER $UNAME
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT [ "/app/orca-registry" ]
|
|
@ -2,16 +2,24 @@ listen_address = "127.0.0.1"
|
|||
listen_port = "3000"
|
||||
url = "http://localhost:3000/"
|
||||
|
||||
[ldap]
|
||||
connection_url = "ldap://localhost:389"
|
||||
bind_dn = "cn=admin,dc=planetexpress,dc=com"
|
||||
bind_password = "GoodNewsEveryone"
|
||||
user_base_dn = "ou=people,dc=planetexpress,dc=com"
|
||||
group_base_dn = "ou=people,dc=planetexpress,dc=com"
|
||||
[storage]
|
||||
driver = "filesystem"
|
||||
path = "/app/blobs"
|
||||
|
||||
user_search_filter = "(&(objectClass=person)(mail=%s))"
|
||||
group_search_filter = "(&(objectclass=groupOfNames)(member=%d))"
|
||||
[database]
|
||||
type = "sqlite"
|
||||
path = "/app/orca.db"
|
||||
|
||||
admin_filter = "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)"
|
||||
#[ldap]
|
||||
#connection_url = "ldap://localhost:389"
|
||||
#bind_dn = "cn=admin,dc=planetexpress,dc=com"
|
||||
#bind_password = "GoodNewsEveryone"
|
||||
#user_base_dn = "ou=people,dc=planetexpress,dc=com"
|
||||
#group_base_dn = "ou=people,dc=planetexpress,dc=com"
|
||||
#
|
||||
#user_search_filter = "(&(objectClass=person)(mail=%s))"
|
||||
#group_search_filter = "(&(objectclass=groupOfNames)(member=%d))"
|
||||
#
|
||||
#admin_filter = "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)"
|
||||
#login_attribute = "mail"
|
||||
#display_name_attribute = "displayName"
|
|
@ -36,7 +36,7 @@ pub async fn list_repositories(Query(params): Query<ListRepositoriesParams>, sta
|
|||
let last_repo = repos.last().and_then(|s| Some(s.clone()));
|
||||
|
||||
// Construct the link header
|
||||
let url = &state.config.get_url();
|
||||
let url = &state.config.url();
|
||||
let mut url = format!("<{}/v2/_catalog?n={}", url, limit);
|
||||
if let Some(last_repo) = last_repo {
|
||||
url += &format!("&limit={}", last_repo);
|
||||
|
|
|
@ -37,7 +37,7 @@ pub async fn list_tags(Path((name, )): Path<(String, )>, Query(params): Query<Li
|
|||
let last_tag = tags.last();
|
||||
|
||||
// Construct the link header
|
||||
let url = &state.config.get_url();
|
||||
let url = &state.config.url();
|
||||
let mut url = format!("<{}/v2/{}/tags/list?n={}", url, name, limit);
|
||||
if let Some(last_tag) = last_tag {
|
||||
url += &format!("&limit={}", last_tag.name);
|
||||
|
|
|
@ -85,7 +85,7 @@ pub async fn chunked_upload_layer_patch(Path((name, layer_uuid)): Path<(String,
|
|||
(0, written_size)
|
||||
};
|
||||
|
||||
let full_uri = format!("{}/v2/{}/blobs/uploads/{}", state.config.get_url(), name, layer_uuid);
|
||||
let full_uri = format!("{}/v2/{}/blobs/uploads/{}", state.config.url(), name, layer_uuid);
|
||||
Ok((
|
||||
StatusCode::ACCEPTED,
|
||||
[
|
||||
|
|
|
@ -91,7 +91,7 @@ impl Deref for AuthToken {
|
|||
type Rejection = (StatusCode, HeaderMap);
|
||||
|
||||
pub async fn require_auth<B>(State(state): State<Arc<AppState>>, mut request: Request<B>, next: Next<B>) -> Result<Response, Rejection> {
|
||||
let bearer = format!("Bearer realm=\"{}/auth\"", state.config.get_url());
|
||||
let bearer = format!("Bearer realm=\"{}/auth\"", state.config.url());
|
||||
let mut failure_headers = HeaderMap::new();
|
||||
failure_headers.append(header::WWW_AUTHENTICATE, bearer.parse().unwrap());
|
||||
failure_headers.append(HeaderName::from_static("docker-distribution-api-version"), "registry/2.0".parse().unwrap());
|
||||
|
@ -119,7 +119,7 @@ pub async fn require_auth<B>(State(state): State<Arc<AppState>>, mut request: Re
|
|||
|
||||
Ok(next.run(request).await)
|
||||
} else {
|
||||
let bearer = format!("Bearer realm=\"{}/auth\"", state.config.get_url());
|
||||
let bearer = format!("Bearer realm=\"{}/auth\"", state.config.url());
|
||||
Ok((
|
||||
StatusCode::UNAUTHORIZED,
|
||||
[
|
||||
|
@ -134,7 +134,7 @@ pub async fn require_auth<B>(State(state): State<Arc<AppState>>, mut request: Re
|
|||
/// The www-authenticate header is set to notify the client of where to authorize with.
|
||||
#[inline(always)]
|
||||
pub fn unauthenticated_response(config: &Config) -> Response {
|
||||
let bearer = format!("Bearer realm=\"{}/auth\"", config.get_url());
|
||||
let bearer = format!("Bearer realm=\"{}/auth\"", config.url());
|
||||
(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
[
|
||||
|
|
|
@ -29,12 +29,36 @@ fn default_display_name_attribute() -> String {
|
|||
"displayName".to_string()
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct FilesystemDriverConfig {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(tag = "driver", rename_all = "snake_case")]
|
||||
pub enum StorageConfig {
|
||||
Filesystem(FilesystemDriverConfig),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct SqliteDbConfig {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum DatabaseConfig {
|
||||
Sqlite(SqliteDbConfig),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
pub listen_address: String,
|
||||
pub listen_port: String,
|
||||
pub url: Option<String>,
|
||||
url: Option<String>,
|
||||
pub ldap: Option<LdapConnectionConfig>,
|
||||
pub database: DatabaseConfig,
|
||||
pub storage: StorageConfig,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -72,7 +96,7 @@ impl Config {
|
|||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn get_url(&self) -> String {
|
||||
pub fn url(&self) -> String {
|
||||
match &self.url {
|
||||
Some(u) => u.clone(),
|
||||
None => format!("http://{}:{}", self.listen_address, self.listen_port)
|
||||
|
|
20
src/main.rs
20
src/main.rs
|
@ -18,9 +18,10 @@ use axum::middleware::Next;
|
|||
use axum::response::Response;
|
||||
use axum::{Router, routing};
|
||||
use axum::ServiceExt;
|
||||
use sqlx::ConnectOptions;
|
||||
use tower_layer::Layer;
|
||||
|
||||
use sqlx::sqlite::SqlitePoolOptions;
|
||||
use sqlx::sqlite::{SqlitePoolOptions, SqliteConnectOptions, SqliteJournalMode};
|
||||
use tokio::sync::Mutex;
|
||||
use tower_http::normalize_path::NormalizePathLayer;
|
||||
use tracing::{debug, Level};
|
||||
|
@ -31,7 +32,7 @@ use database::Database;
|
|||
use crate::storage::StorageDriver;
|
||||
use crate::storage::filesystem::FilesystemDriver;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::config::{Config, DatabaseConfig, StorageConfig};
|
||||
|
||||
use tower_http::trace::TraceLayer;
|
||||
|
||||
|
@ -68,12 +69,22 @@ async fn main() -> anyhow::Result<()> {
|
|||
let config = Config::new()
|
||||
.expect("Failure to parse config!");
|
||||
|
||||
let sqlite_config = match &config.database {
|
||||
DatabaseConfig::Sqlite(sqlite) => sqlite,
|
||||
};
|
||||
|
||||
let connection_options = SqliteConnectOptions::from_str(&format!("sqlite://{}", &sqlite_config.path))?
|
||||
.journal_mode(SqliteJournalMode::Wal);
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.max_connections(15)
|
||||
.connect("test.db").await?;
|
||||
.connect_with(connection_options).await?;
|
||||
pool.create_schema().await?;
|
||||
|
||||
let storage_driver: Mutex<Box<dyn StorageDriver>> = Mutex::new(Box::new(FilesystemDriver::new("registry/blobs")));
|
||||
let storage_driver: Mutex<Box<dyn StorageDriver>> = match &config.storage {
|
||||
StorageConfig::Filesystem(fs) => {
|
||||
Mutex::new(Box::new(FilesystemDriver::new(&fs.path)))
|
||||
}
|
||||
};
|
||||
|
||||
// figure out the auth driver depending on whats specified in the config,
|
||||
// the fallback is a database auth driver.
|
||||
|
@ -91,7 +102,6 @@ async fn main() -> anyhow::Result<()> {
|
|||
|
||||
let state = Arc::new(AppState::new(pool, storage_driver, config, auth_driver));
|
||||
|
||||
|
||||
let auth_middleware = axum::middleware::from_fn_with_state(state.clone(), auth::require_auth);
|
||||
let path_middleware = axum::middleware::from_fn(change_request_paths);
|
||||
|
||||
|
|
Loading…
Reference in New Issue