init
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
/.idea
|
39
.gitlab-ci.yml
Normal file
39
.gitlab-ci.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
stages:
|
||||
- generate_image
|
||||
- deploy
|
||||
- notify
|
||||
|
||||
variables:
|
||||
BUILD_NAME: "rust/storage"
|
||||
SERVICE_NAME: "storage"
|
||||
IMAGE_TAG: "dev"
|
||||
|
||||
generate_image_dev:
|
||||
stage: generate_image
|
||||
image: docker:20.10.2
|
||||
script:
|
||||
- docker build -t $BUILD_NAME:$IMAGE_TAG .
|
||||
tags:
|
||||
- '001'
|
||||
only:
|
||||
- dev
|
||||
except:
|
||||
- triggers
|
||||
|
||||
deploy_dev:
|
||||
|
||||
stage: deploy
|
||||
image: docker:20.10.2
|
||||
script:
|
||||
- CONTAINER_NAME=$(docker ps -aq --filter name=$SERVICE_NAME)
|
||||
- echo $CONTAINER_NAME
|
||||
- if [[ -n "$CONTAINER_NAME" ]]; then
|
||||
docker rm -f $CONTAINER_NAME;
|
||||
fi
|
||||
- docker run -d --name $SERVICE_NAME $BUILD_NAME:$IMAGE_TAG
|
||||
tags:
|
||||
- '001'
|
||||
only:
|
||||
- dev
|
||||
except:
|
||||
- triggers
|
2198
Cargo.lock
generated
Normal file
2198
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "web"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bytes = "1.5.0"
|
||||
actix-web = "4.4.1"
|
||||
actix-cors = "0.7.0"
|
||||
rust-s3 ={version = "0.33.0" }
|
||||
tokio = { version = "1.0.0", features = ["rt", "rt-multi-thread", "macros"] }
|
||||
serde = "1.0.195"
|
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM rust:1.75 as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN USER=root cargo new storage
|
||||
WORKDIR /app/storage
|
||||
RUN mkdir -p .cargo
|
||||
COPY config.toml .cargo/
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
|
||||
COPY ./src src
|
||||
RUN cargo install --path . --color always
|
||||
|
||||
# Copy the app to an base Docker image, here we use distroless
|
||||
FROM ubuntu:latest
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y ca-certificates
|
||||
RUN update-ca-certificates
|
||||
RUN mkdir -p /cache
|
||||
COPY --from=builder /usr/local/cargo/bin/storage /storage
|
||||
USER 1000
|
||||
ENTRYPOINT ["/storage"]
|
14
config.toml
Normal file
14
config.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[http]
|
||||
check-revoke = false
|
||||
|
||||
[source.crates-io]
|
||||
replace-with='rsproxy'
|
||||
|
||||
[source.rsproxy]
|
||||
registry="https://rsproxy.cn/crates.io-index"
|
||||
|
||||
[registries.rsproxy]
|
||||
index = "https://rsproxy.cn/crates.io-index"
|
||||
|
||||
[net]
|
||||
git-fetch-with-cli = true
|
135
src/main.rs
Normal file
135
src/main.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use std::env;
|
||||
use actix_cors::Cors;
|
||||
use actix_web::{App, HttpServer, Responder, get, put, post, delete, web, CustomizeResponder};
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::Json;
|
||||
use bytes::Bytes;
|
||||
use serde::Serialize;
|
||||
|
||||
mod mods;
|
||||
|
||||
use mods::MinioPrivate;
|
||||
use crate::mods::FileManager;
|
||||
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Response {
|
||||
error_code: u16,
|
||||
error_message: String,
|
||||
}
|
||||
|
||||
|
||||
fn json_response(status_code: u16) -> CustomizeResponder<Json<Response>> {
|
||||
let message = match status_code {
|
||||
200 => { "Success!" }
|
||||
201 => { "Create success!" }
|
||||
204 => { "Delete success!" }
|
||||
205 => { "Update success!" }
|
||||
404 => { "Not exists!" }
|
||||
409 => { "Resource conflict!" }
|
||||
500 => { "Server error!" }
|
||||
_ => { "Server error!" }
|
||||
};
|
||||
let resp = Response { error_code: status_code, error_message: message.to_string() };
|
||||
Json(resp)
|
||||
.customize()
|
||||
.with_status(StatusCode::from_u16(status_code).unwrap())
|
||||
.insert_header(("content-type", "application/json"))
|
||||
}
|
||||
|
||||
fn bytes_response(file_content: Bytes, mut file_type: String) -> CustomizeResponder<Bytes> {
|
||||
if file_type == "" {
|
||||
file_type = "application/octet-stream".to_string();
|
||||
}
|
||||
return if file_content.is_empty() {
|
||||
Bytes::from_static(b"{\"error_code\": 404,\"error_message\": \"Not exists!\"}")
|
||||
.customize()
|
||||
.with_status(StatusCode::from_u16(404).unwrap())
|
||||
.insert_header(("content-type", "application/json"))
|
||||
} else {
|
||||
file_content
|
||||
.customize()
|
||||
.with_status(StatusCode::from_u16(200).unwrap())
|
||||
.insert_header(("content-type", file_type))
|
||||
};
|
||||
}
|
||||
|
||||
async fn get_minio() -> MinioPrivate {
|
||||
let region = env::var("MINIO_REGION").unwrap_or("".to_string());
|
||||
let endpoint = env::var("MINIO_ENDPOINT").unwrap_or("http://192.168.1.100:9000".to_string());
|
||||
let access_key = env::var("MINIO_ACCESS_KEY").unwrap_or("sTkzoRR9lQUmI5Hp".to_string());
|
||||
let secret_key = env::var("MINIO_SECRET_KEY").unwrap_or("oDUG9GRlkNv3V76bVGjovp9228bP25WC".to_string());
|
||||
return MinioPrivate {
|
||||
region,
|
||||
endpoint,
|
||||
access_key,
|
||||
secret_key,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#[get("/healthy")]
|
||||
async fn healthy() -> impl Responder {
|
||||
"Ok!".to_string()
|
||||
}
|
||||
|
||||
#[post("/{bucket_name}/{file_path:.*}")]
|
||||
async fn create_file(path: web::Path<(String, String)>, body: web::Payload) -> impl Responder {
|
||||
let minio = get_minio().await;
|
||||
let path_parameters = path.into_inner();
|
||||
let work_dir = path_parameters.0;
|
||||
let path_name = path_parameters.1;
|
||||
let file_content = body.to_bytes().await.unwrap();
|
||||
let status_code = minio.create(work_dir.as_str(), path_name.as_str(), file_content).await;
|
||||
json_response(status_code)
|
||||
}
|
||||
|
||||
#[put("/{bucket_name}/{file_path:.*}")]
|
||||
async fn update_file(path: web::Path<(String, String)>, body: web::Payload) -> impl Responder {
|
||||
let minio = get_minio().await;
|
||||
let path_parameters = path.into_inner();
|
||||
let work_dir = path_parameters.0;
|
||||
let path_name = path_parameters.1;
|
||||
let file_content = body.to_bytes().await.unwrap();
|
||||
let status_code = minio.update(work_dir.as_str(), path_name.as_str(), file_content).await;
|
||||
json_response(status_code)
|
||||
}
|
||||
|
||||
#[get("/{bucket_name}/{file_path:.*}")]
|
||||
async fn get_file(path: web::Path<(String, String)>) -> impl Responder {
|
||||
let minio = get_minio().await;
|
||||
let path_parameters = path.into_inner();
|
||||
let work_dir = path_parameters.0;
|
||||
let path_name = path_parameters.1;
|
||||
|
||||
let result = minio.get(work_dir.as_str(), path_name.as_str()).await;
|
||||
bytes_response(result, "".to_string())
|
||||
}
|
||||
|
||||
#[delete("/{bucket_name}/{file_path:.*}")]
|
||||
async fn delete_file(path: web::Path<(String, String)>) -> impl Responder {
|
||||
let minio = get_minio().await;
|
||||
let path_parameters = path.into_inner();
|
||||
let work_dir = path_parameters.0;
|
||||
let path_name = path_parameters.1;
|
||||
let status_code = minio.delete(work_dir.as_str(), path_name.as_str()).await;
|
||||
json_response(status_code)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
HttpServer::new(|| {
|
||||
let cors = Cors::permissive();
|
||||
App::new()
|
||||
.wrap(cors)
|
||||
.service(healthy)
|
||||
.service(get_file)
|
||||
.service(create_file)
|
||||
.service(update_file)
|
||||
.service(delete_file)
|
||||
})
|
||||
.bind(("0.0.0.0", 8080))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
86
src/mods.rs
Normal file
86
src/mods.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use bytes::Bytes;
|
||||
use s3::bucket::Bucket;
|
||||
use s3::region::Region;
|
||||
use s3::creds::Credentials;
|
||||
use core::time::Duration;
|
||||
|
||||
|
||||
pub trait FileManager {
|
||||
async fn exists(&self, work_dir: &str, file_path: &str) -> u16;
|
||||
async fn get(&self, work_dir: &str, file_path: &str) -> Bytes;
|
||||
async fn create(&self, work_dir: &str, file_path: &str, file_content: Bytes) -> u16;
|
||||
async fn update(&self, work_dir: &str, file_path: &str, file_content: Bytes) -> u16;
|
||||
|
||||
async fn delete(&self, work_dir: &str, file_path: &str) -> u16;
|
||||
async fn _bucket(&self, bucket_name: &str) -> Bucket;
|
||||
}
|
||||
|
||||
pub struct MinioPrivate {
|
||||
pub region: String,
|
||||
pub endpoint: String,
|
||||
pub access_key:String,
|
||||
pub secret_key: String,
|
||||
}
|
||||
|
||||
impl FileManager for MinioPrivate {
|
||||
async fn exists(&self, work_dir: &str, file_path: &str) -> u16 {
|
||||
let bucket = self._bucket(work_dir).await;
|
||||
return match bucket.head_object(file_path).await {
|
||||
Ok(_) => { 200 }
|
||||
Err(_) => { 404 }
|
||||
};
|
||||
}
|
||||
async fn get(&self, work_dir: &str, file_path: &str) -> Bytes {
|
||||
let bucket = self._bucket(work_dir).await;
|
||||
return match bucket.get_object(file_path).await {
|
||||
Ok(value) => {
|
||||
Bytes::from(value.to_vec())
|
||||
}
|
||||
Err(_) => {
|
||||
Bytes::new()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async fn create(&self, work_dir: &str, file_path: &str, file_content: Bytes) -> u16 {
|
||||
if self.exists(work_dir, file_path).await == 200 {
|
||||
return 409;
|
||||
}
|
||||
let bucket = self._bucket(work_dir).await;
|
||||
return match bucket.put_object(file_path, file_content.as_ref()).await {
|
||||
Ok(_) => { 201 }
|
||||
Err(_) => { 500 }
|
||||
};
|
||||
}
|
||||
async fn update(&self, work_dir: &str, file_path: &str, file_content: Bytes) -> u16 {
|
||||
if self.exists(work_dir, file_path).await == 404 {
|
||||
return 404;
|
||||
}
|
||||
let bucket = self._bucket(work_dir).await;
|
||||
|
||||
return match bucket.put_object(file_path, file_content.as_ref()).await {
|
||||
Ok(_) => { 205 }
|
||||
Err(_) => { 500 }
|
||||
};
|
||||
}
|
||||
|
||||
async fn delete(&self, work_dir: &str, file_path: &str) -> u16 {
|
||||
if self.exists(work_dir, file_path).await == 404 {
|
||||
return 404;
|
||||
}
|
||||
let bucket = self._bucket(work_dir).await;
|
||||
return match bucket.delete_object(file_path).await {
|
||||
Ok(_) => { 204 }
|
||||
Err(_) => { 500 }
|
||||
};
|
||||
}
|
||||
|
||||
async fn _bucket(&self, bucket_name: &str) -> Bucket {
|
||||
let region = Region::Custom { region: self.region.clone().into(), endpoint: self.endpoint.clone().into() };
|
||||
let credentials = Credentials::new(Some(self.access_key.as_str()), Some(self.secret_key.as_str()), None, None, None).unwrap();
|
||||
let mut bucket = Bucket::new(bucket_name, region, credentials).unwrap().with_path_style();
|
||||
bucket.set_request_timeout(Option::from(Duration::new(60, 0)));
|
||||
return bucket;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user