Skip to content

Commit

Permalink
feat(dashboard,cli,docs): implement limits (#390)
Browse files Browse the repository at this point in the history
* feat(dashboard,cli,docs): implement limits

* refactor(dashboard,cli): add content-length to presigned URLs

* fix(cli): lint error
  • Loading branch information
QuiiBz authored Dec 16, 2022
1 parent 5ec41ee commit 6c2f557
Show file tree
Hide file tree
Showing 16 changed files with 212 additions and 59 deletions.
6 changes: 6 additions & 0 deletions .changeset/happy-teachers-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@lagon/cli': patch
'@lagon/dashboard': patch
---

Implement limits
5 changes: 5 additions & 0 deletions .changeset/late-dogs-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lagon/docs': patch
---

Add limits page
4 changes: 2 additions & 2 deletions packages/cli/src/commands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub fn build(file: PathBuf, client: Option<PathBuf>, public_dir: Option<PathBuf>
let end_progress = print_progress("Writting index.js...");

fs::create_dir_all(".lagon")?;
fs::write(".lagon/index.js", index.get_ref())?;
fs::write(".lagon/index.js", index)?;

end_progress();

Expand All @@ -35,7 +35,7 @@ pub fn build(file: PathBuf, client: Option<PathBuf>, public_dir: Option<PathBuf>
.join("public")
.join(PathBuf::from(&path).parent().unwrap());
fs::create_dir_all(dir)?;
fs::write(format!(".lagon/public/{}", path), content.get_ref())?;
fs::write(format!(".lagon/public/{}", path), content)?;

end_progress();
}
Expand Down
9 changes: 4 additions & 5 deletions packages/cli/src/commands/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ use std::time::Duration;
use tokio::sync::Mutex;

use crate::utils::{
bundle_function, info, input, success, validate_code_file, validate_public_dir, warn,
FileCursor,
bundle_function, info, input, success, validate_code_file, validate_public_dir, warn, Assets,
};

use log::{
Expand Down Expand Up @@ -76,7 +75,7 @@ fn parse_environment_variables(env: Option<PathBuf>) -> Result<HashMap<String, S
async fn handle_request(
req: HyperRequest<Body>,
ip: String,
content: Arc<Mutex<(FileCursor, HashMap<String, FileCursor>)>>,
content: Arc<Mutex<(Vec<u8>, Assets)>>,
environment_variables: HashMap<String, String>,
) -> Result<HyperResponse<Body>> {
let mut url = req.uri().to_string();
Expand Down Expand Up @@ -119,7 +118,7 @@ async fn handle_request(
let response = Response {
status: 200,
headers: Some(headers),
body: Bytes::from(asset.1.get_ref().to_vec()),
body: Bytes::from(asset.1.clone()),
};

tx.send_async(RunResult::Response(response))
Expand All @@ -131,7 +130,7 @@ async fn handle_request(
request.add_header("X-Forwarded-For".into(), ip);

let mut isolate = Isolate::new(
IsolateOptions::new(String::from_utf8(index.get_ref().to_vec())?)
IsolateOptions::new(String::from_utf8(index)?)
.with_metadata(Some((String::from(""), String::from(""))))
.with_environment_variables(environment_variables),
);
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/commands/undeploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ use crate::utils::{

#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
struct DeleteDeploymentRequest {
struct UndeployDeploymentRequest {
function_id: String,
deployment_id: String,
}

#[derive(Deserialize, Debug)]
struct DeleteDeploymentResponse {
struct UndeployDeploymentResponse {
#[allow(dead_code)]
ok: bool,
}
Expand All @@ -41,9 +41,9 @@ pub async fn undeploy(file: PathBuf, deployment_id: String) -> Result<()> {
true => {
let end_progress = print_progress("Deleting Deployment...");
TrpcClient::new(&config)
.mutation::<DeleteDeploymentRequest, DeleteDeploymentResponse>(
"deploymentDelete",
DeleteDeploymentRequest {
.mutation::<UndeployDeploymentRequest, UndeployDeploymentResponse>(
"deploymentUndeploy",
UndeployDeploymentRequest {
function_id: function_config.function_id,
deployment_id,
},
Expand Down
67 changes: 53 additions & 14 deletions packages/cli/src/utils/deployments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,19 @@ use hyper::{Body, Method, Request};
use std::{
collections::HashMap,
fs,
io::Cursor,
path::{Path, PathBuf},
process::Command,
};
use walkdir::WalkDir;
use walkdir::{DirEntry, WalkDir};

use pathdiff::diff_paths;
use serde::{Deserialize, Serialize};

use crate::utils::{debug, print_progress, success, TrpcClient};

use super::Config;
use super::{Config, MAX_ASSETS_PER_FUNCTION, MAX_ASSET_SIZE_MB, MAX_FUNCTION_SIZE_MB};

pub type FileCursor = Cursor<Vec<u8>>;
pub type Assets = HashMap<String, Vec<u8>>;

#[derive(Serialize, Deserialize, Debug)]
pub struct DeploymentConfig {
Expand Down Expand Up @@ -73,7 +72,7 @@ pub fn delete_function_config(file: &Path) -> Result<()> {
Ok(())
}

fn esbuild(file: &PathBuf) -> Result<FileCursor> {
fn esbuild(file: &PathBuf) -> Result<Vec<u8>> {
let result = Command::new("esbuild")
.arg(file)
.arg("--bundle")
Expand All @@ -87,7 +86,14 @@ fn esbuild(file: &PathBuf) -> Result<FileCursor> {
if result.status.success() {
let output = result.stdout;

return Ok(Cursor::new(output));
if output.len() >= MAX_FUNCTION_SIZE_MB {
return Err(anyhow!(
"Function can't be larger than {} bytes",
MAX_FUNCTION_SIZE_MB
));
}

return Ok(output);
}

Err(anyhow!(
Expand All @@ -101,7 +107,7 @@ pub fn bundle_function(
index: &PathBuf,
client: &Option<PathBuf>,
public_dir: &PathBuf,
) -> Result<(FileCursor, HashMap<String, FileCursor>)> {
) -> Result<(Vec<u8>, Assets)> {
if Command::new("esbuild").arg("--version").output().is_err() {
return Err(anyhow!(
"esbuild is not installed. Please install it with `npm install -g esbuild`",
Expand All @@ -112,7 +118,7 @@ pub fn bundle_function(
let index_output = esbuild(index)?;
end_progress();

let mut assets = HashMap::<String, FileCursor>::new();
let mut assets = Assets::new();

if let Some(client) = client {
let end_progress = print_progress("Bundling client file...");
Expand All @@ -139,19 +145,38 @@ pub fn bundle_function(
);
let end_progress = print_progress(&msg);

for file in WalkDir::new(public_dir) {
let files = WalkDir::new(public_dir)
.into_iter()
.collect::<Vec<walkdir::Result<DirEntry>>>();

if files.len() >= MAX_ASSETS_PER_FUNCTION {
return Err(anyhow!(
"Too many assets in public directory, max is {}",
MAX_ASSETS_PER_FUNCTION
));
}

for file in files {
let file = file?;
let path = file.path();

if path.is_file() {
if path.metadata()?.len() >= MAX_ASSET_SIZE_MB {
return Err(anyhow!(
"File {:?} can't be larger than {} bytes",
path,
MAX_ASSET_SIZE_MB
));
}

let diff = diff_paths(path, public_dir)
.unwrap()
.to_str()
.unwrap()
.to_string();
let file_content = fs::read(path)?;

assets.insert(diff, Cursor::new(file_content));
assets.insert(diff, file_content);
}
}

Expand All @@ -163,11 +188,18 @@ pub fn bundle_function(
Ok((index_output, assets))
}

#[derive(Serialize, Debug)]
struct Asset {
name: String,
size: usize,
}

#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
struct CreateDeploymentRequest {
function_id: String,
assets: Vec<String>,
function_size: usize,
assets: Vec<Asset>,
}

#[derive(Deserialize, Debug)]
Expand Down Expand Up @@ -209,7 +241,14 @@ pub async fn create_deployment(
"deploymentCreate",
CreateDeploymentRequest {
function_id: function_id.clone(),
assets: assets.keys().cloned().collect(),
function_size: index.len(),
assets: assets
.iter()
.map(|(key, value)| Asset {
name: key.clone(),
size: value.len(),
})
.collect(),
},
)
.await?;
Expand All @@ -227,7 +266,7 @@ pub async fn create_deployment(
let request = Request::builder()
.method(Method::PUT)
.uri(code_url)
.body(Body::from(index.into_inner()))?;
.body(Body::from(index))?;

trpc_client.client.request(request).await?;

Expand All @@ -240,7 +279,7 @@ pub async fn create_deployment(
let request = Request::builder()
.method(Method::PUT)
.uri(url)
.body(Body::from(asset.clone().into_inner()))?;
.body(Body::from(asset.clone()))?;

trpc_client.client.request(request).await?;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ pub use console::*;
pub use deployments::*;
pub use trpc::*;

pub const MAX_FUNCTION_SIZE_MB: usize = 10 * 1024 * 1024; // 10MB
pub const MAX_ASSET_SIZE_MB: u64 = 10 * 1024 * 1024; // 10MB
pub const MAX_ASSETS_PER_FUNCTION: usize = 100;

pub fn validate_code_file(file: &Path) -> Result<()> {
if !file.exists() || !file.is_file() {
return Err(anyhow!("{} is not a file", file.to_str().unwrap()));
Expand Down
26 changes: 22 additions & 4 deletions packages/dashboard/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
export const ORGANIZATION_NAME_MIN_LENGTH = 3;
export const ORGANIZATION_NAME_MAX_LENGTH = 20;
export const ORGANIZATION_DESCRIPTION_MAX_LENGTH = 200;
export const ORGANIZATION_NAME_MAX_LENGTH = 64;
export const ORGANIZATION_DESCRIPTION_MAX_LENGTH = 256;

export const FUNCTION_NAME_MIN_LENGTH = 5;
export const FUNCTION_NAME_MAX_LENGTH = 20;
export const FUNCTION_NAME_MIN_LENGTH = 3;
export const FUNCTION_NAME_MAX_LENGTH = 64;

export const FUNCTION_DEFAULT_MEMORY = 128; // 128MB
export const FUNCTION_DEFAULT_TIMEOUT = 50; // 50ms
export const FUNCTION_DEFAULT_STARTUP_TIMEOUT = 200; // 200ms
export const MAX_FUNCTIONS_PER_ORGANIZATION = 20;

export const MAX_FUNCTION_SIZE_MB = 10 * 1024 * 1024; // 10MB
export const MAX_ASSET_SIZE_MB = 10 * 1024 * 1024; // 10MB
export const MAX_ASSETS_PER_FUNCTION = 100;

export const ENVIRONMENT_VARIABLE_KEY_MAX_LENGTH = 64;
export const ENVIRONMENT_VARIABLE_VALUE_MAX_SIZE = 5 * 1024; // 5KB
export const ENVIRONMENT_VARIABLES_PER_FUNCTION = 100;

export const CUSTOM_DOMAINS_PER_FUNCTION = 10;

export const PRESIGNED_URL_EXPIRES_SECONDS = 60 * 60; // 1 hour

export const REGIONS = {
'ashburn-us-east': 'Ashburn (us-east)',
'hillsboro-us-west': 'Hillsboro (us-west)',
Expand All @@ -22,3 +36,7 @@ export const REGIONS = {
};

export type Regions = keyof typeof REGIONS;

export const DEFAULT_FUNCTION = `export function handler(request) {
return new Response("Hello World!")
}`;
17 changes: 10 additions & 7 deletions packages/dashboard/lib/pages/function/FunctionDeployments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ type FunctionDeploymentsProps = {
const FunctionDeployments = ({ func, refetch }: FunctionDeploymentsProps) => {
const { scopedT } = useI18n();
const t = scopedT('functions.deployments');
const deleteDeployment = trpc.deploymentDelete.useMutation();
const undeployDeployment = trpc.deploymentUndeploy.useMutation();
const promoteDeployment = trpc.deploymentPromote.useMutation();

const removeDeplomyent = useCallback(
async (deployment: { id: string }) => {
await deleteDeployment.mutateAsync({
await undeployDeployment.mutateAsync({
functionId: func?.id || '',
deploymentId: deployment.id,
});

await refetch();
toast.success(t('delete.success'));
},
[func?.id, deleteDeployment, refetch, t],
[func?.id, undeployDeployment, refetch, t],
);

const promoteDeploymentHandler = useCallback(
Expand Down Expand Up @@ -106,7 +106,10 @@ const FunctionDeployments = ({ func, refetch }: FunctionDeploymentsProps) => {
title={t('promote.modal.title')}
description={t('promote.modal.description')}
disclosure={
<Button leftIcon={<ArrowPathIcon className="w-4 h-4" />} disabled={deleteDeployment.isLoading}>
<Button
leftIcon={<ArrowPathIcon className="w-4 h-4" />}
disabled={undeployDeployment.isLoading}
>
{t('promote')}
</Button>
}
Expand All @@ -122,17 +125,17 @@ const FunctionDeployments = ({ func, refetch }: FunctionDeploymentsProps) => {
title={t('delete.modal.title')}
description={t('delete.modal.description')}
disclosure={
<Button variant="danger" disabled={deleteDeployment.isLoading}>
<Button variant="danger" disabled={undeployDeployment.isLoading}>
{t('delete')}
</Button>
}
>
<Dialog.Buttons>
<Dialog.Cancel disabled={deleteDeployment.isLoading} />
<Dialog.Cancel disabled={undeployDeployment.isLoading} />
<Dialog.Action
variant="danger"
onClick={() => removeDeplomyent(deployment)}
disabled={deleteDeployment.isLoading}
disabled={undeployDeployment.isLoading}
>
{t('delete.modal.submit')}
</Dialog.Action>
Expand Down
Loading

0 comments on commit 6c2f557

Please sign in to comment.