tools/unitctl: implement application subcommand
* application subcommand UI schema * application subcommand handler * additions to unit-client-rs to expose application API * elaborate on OpenAPI error handling * adds wasm and wasi app schemas to OpenAPI Schema * updates tools/unitctl OpenAPI library * many linter fixes * README.md updates Signed-off-by: Ava Hahn <a.hahn@f5.com>
This commit is contained in:
parent
d96d583328
commit
e0c15ae457
15 changed files with 335 additions and 137 deletions
|
@ -4730,6 +4730,9 @@ components:
|
|||
isolation:
|
||||
rootfs: "/www/"
|
||||
|
||||
wasiapp:
|
||||
type: "wasm-wasi-component"
|
||||
|
||||
# /config/listeners
|
||||
configListeners:
|
||||
summary: "Multiple listeners"
|
||||
|
@ -5304,6 +5307,8 @@ components:
|
|||
- $ref: "#/components/schemas/configApplicationPHP"
|
||||
- $ref: "#/components/schemas/configApplicationPython"
|
||||
- $ref: "#/components/schemas/configApplicationRuby"
|
||||
- $ref: "#/components/schemas/configApplicationWasm"
|
||||
- $ref: "#/components/schemas/configApplicationWasi"
|
||||
|
||||
discriminator:
|
||||
propertyName: type
|
||||
|
@ -5314,6 +5319,8 @@ components:
|
|||
php: "#/components/schemas/configApplicationPHP"
|
||||
python: "#/components/schemas/configApplicationPython"
|
||||
ruby: "#/components/schemas/configApplicationRuby"
|
||||
wasm: "#/components/schemas/configApplicationWasm"
|
||||
wasm-wasi-component: "#/components/schemas/configApplicationWasi"
|
||||
|
||||
# ABSTRACT BASE SCHEMA, NOT PRESENT IN THE CONFIGURATION; STORES COMMON OPTIONS
|
||||
configApplicationCommon:
|
||||
|
@ -5326,7 +5333,7 @@ components:
|
|||
type:
|
||||
type: string
|
||||
description: "Application type and language version."
|
||||
enum: [external, java, perl, php, python, ruby]
|
||||
enum: [external, java, perl, php, python, ruby, wasm, wasm-wasi-component]
|
||||
|
||||
environment:
|
||||
type: object
|
||||
|
@ -5592,6 +5599,82 @@ components:
|
|||
description: "Number of worker threads per app process."
|
||||
default: 1
|
||||
|
||||
configApplicationWasm:
|
||||
description: "WASM application on Unit."
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/configApplicationCommon"
|
||||
- type: object
|
||||
required:
|
||||
- module
|
||||
- request_handler
|
||||
- malloc_handler
|
||||
- free_handler
|
||||
|
||||
properties:
|
||||
module:
|
||||
type: string
|
||||
description: "Path to WebAssembly module."
|
||||
|
||||
request_handler:
|
||||
type: string
|
||||
description: "Name of request handling function."
|
||||
|
||||
malloc_handler:
|
||||
type: string
|
||||
description: "Name of memory allocator function."
|
||||
|
||||
free_handler:
|
||||
type: string
|
||||
description: "Name of memory free function."
|
||||
|
||||
access:
|
||||
type: object
|
||||
properties:
|
||||
filesystem:
|
||||
$ref: "#/components/schemas/stringArray"
|
||||
description: "Host directories this application may have access to."
|
||||
|
||||
module_init_handler:
|
||||
type: string
|
||||
description: "Name of function called to initialize module."
|
||||
|
||||
module_end_handler:
|
||||
type: string
|
||||
description: "Name of function called to teardown module."
|
||||
|
||||
request_init_handler:
|
||||
type: string
|
||||
description: "Name of function called to initialize request."
|
||||
|
||||
request_end_handler:
|
||||
type: string
|
||||
description: "Name of function called to teardown request."
|
||||
|
||||
response_end_handler:
|
||||
type: string
|
||||
description: "Name of function called to teardown response."
|
||||
|
||||
|
||||
configApplicationWasi:
|
||||
description: "WASI application on Unit."
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/configApplicationCommon"
|
||||
- type: object
|
||||
required:
|
||||
- component
|
||||
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
description: "Path to wasm wasi component application."
|
||||
|
||||
access:
|
||||
type: object
|
||||
properties:
|
||||
filesystem:
|
||||
$ref: "#/components/schemas/stringArray"
|
||||
description: "Host directories this application may have access to."
|
||||
|
||||
configApplicationPHP:
|
||||
description: "PHP application on Unit."
|
||||
allOf:
|
||||
|
|
|
@ -23,7 +23,7 @@ CARGO ?= cargo
|
|||
DOCKER ?= docker
|
||||
DOCKER_BUILD_FLAGS ?= --load
|
||||
CHECKSUM ?= sha256sum
|
||||
OPENAPI_GENERATOR_VERSION ?= 6.6.0
|
||||
OPENAPI_GENERATOR_VERSION ?= 7.6.0
|
||||
|
||||
# Define platform targets based off of the current host OS
|
||||
# If running MacOS, then build for MacOS platform targets installed in rustup
|
||||
|
@ -137,7 +137,7 @@ openapi-clean: ## Clean up generated OpenAPI files
|
|||
$Q find "$(CURDIR)/unit-openapi/src/apis" \
|
||||
! -name 'error.rs' -type f -exec rm -f {} +
|
||||
$Q $(info $(M) cleaning up generated OpenAPI models code)
|
||||
$Q rm -rf "$(CURDIR)/unit-openapi/src/models/*"
|
||||
$Q rm -rf "$(CURDIR)/unit-openapi/src/models"
|
||||
|
||||
include $(CURDIR)/build/package.mk
|
||||
include $(CURDIR)/build/container.mk
|
||||
|
|
|
@ -31,13 +31,13 @@ their own makefile targets. Alternatively, all available binary targets can be
|
|||
built with `make all`. See the below example for illustration:
|
||||
|
||||
```
|
||||
[ava@calliope cli]$ make list-targets
|
||||
$ make list-targets
|
||||
x86_64-unknown-linux-gnu
|
||||
|
||||
[ava@calliope cli]$ make x86_64-unknown-linux-gnu
|
||||
$ make x86_64-unknown-linux-gnu
|
||||
▶ building unitctl with flags [--quiet --release --bin unitctl --target x86_64-unknown-linux-gnu]
|
||||
|
||||
[ava@calliope cli]$ file ./target/x86_64-unknown-linux-gnu/release/unitctl
|
||||
$ file ./target/x86_64-unknown-linux-gnu/release/unitctl
|
||||
./target/x86_64-unknown-linux-gnu/release/unitctl: ELF 64-bit LSB pie executable,
|
||||
x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
|
||||
BuildID[sha1]=ef4b094ffd549b39a8cb27a7ba2cc0dbad87a3bc, for GNU/Linux 4.4.0,
|
||||
|
@ -91,6 +91,26 @@ To the subcommand `unitctl instances new` the user must provide three things:
|
|||
|
||||
After deployment the user will have one Unit container running on the host network.
|
||||
|
||||
### Lists active applications and provides means to restart them
|
||||
Listing applications:
|
||||
```
|
||||
$ unitctl app list
|
||||
{
|
||||
"wasm": {
|
||||
"type": "wasm-wasi-component",
|
||||
"component": "/www/wasmapp-proxy-component.wasm"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Restarting an application:
|
||||
```
|
||||
$ unitctl app reload wasm
|
||||
{
|
||||
"success": "Ok"
|
||||
}
|
||||
```
|
||||
|
||||
### Lists active listeners from running Unit processes
|
||||
```
|
||||
unitctl listeners
|
||||
|
|
|
@ -34,7 +34,6 @@ impl ControlSocketScheme {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl Display for ControlSocket {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
@ -312,7 +311,6 @@ impl ControlSocket {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
|
|
|
@ -15,9 +15,11 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::control_socket_address::ControlSocket;
|
||||
use unit_openapi::apis::configuration::Configuration;
|
||||
use unit_openapi::apis::{Error as OpenAPIError, StatusApi};
|
||||
use unit_openapi::apis::{ListenersApi, ListenersApiClient, StatusApiClient};
|
||||
use unit_openapi::models::{ConfigListener, Status};
|
||||
use unit_openapi::apis::{
|
||||
ApplicationsApi, ApplicationsApiClient, AppsApi, AppsApiClient, Error as OpenAPIError, ListenersApi,
|
||||
ListenersApiClient, StatusApi, StatusApiClient,
|
||||
};
|
||||
use unit_openapi::models::{ConfigApplication, ConfigListener, Status};
|
||||
|
||||
const USER_AGENT: &str = concat!("UNIT CLI/", env!("CARGO_PKG_VERSION"), "/rust");
|
||||
|
||||
|
@ -276,6 +278,46 @@ impl UnitClient {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn applications_api(&self) -> Box<dyn ApplicationsApi + 'static> {
|
||||
new_openapi_client!(self, ApplicationsApiClient, ApplicationsApi)
|
||||
}
|
||||
|
||||
pub async fn applications(&self) -> Result<HashMap<String, ConfigApplication>, Box<UnitClientError>> {
|
||||
self.applications_api().get_applications().await.or_else(|err| {
|
||||
if let OpenAPIError::Hyper(hyper_error) = err {
|
||||
Err(Box::new(UnitClientError::new(
|
||||
hyper_error,
|
||||
self.control_socket.to_string(),
|
||||
"/applications".to_string(),
|
||||
)))
|
||||
} else {
|
||||
Err(Box::new(UnitClientError::OpenAPIError { source: err }))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn per_application_api(&self) -> Box<dyn AppsApi + 'static> {
|
||||
new_openapi_client!(self, AppsApiClient, AppsApi)
|
||||
}
|
||||
|
||||
pub async fn restart_application(&self, name: &String) -> Result<HashMap<String, String>, Box<UnitClientError>> {
|
||||
self.per_application_api()
|
||||
.await
|
||||
.get_app_restart(name.as_str())
|
||||
.await
|
||||
.or_else(|err| {
|
||||
if let OpenAPIError::Hyper(hyper_error) = err {
|
||||
Err(Box::new(UnitClientError::new(
|
||||
hyper_error,
|
||||
self.control_socket.to_string(),
|
||||
format!("/control/applications/{}/restart", name),
|
||||
)))
|
||||
} else {
|
||||
Err(Box::new(UnitClientError::OpenAPIError { source: err }))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn is_running(&self) -> bool {
|
||||
self.status().await.is_ok()
|
||||
}
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs::read_to_string;
|
||||
use std::path::{PathBuf, MAIN_SEPARATOR};
|
||||
use std::io::stderr;
|
||||
use std::path::{PathBuf, MAIN_SEPARATOR};
|
||||
|
||||
use crate::control_socket_address::ControlSocket;
|
||||
use crate::futures::StreamExt;
|
||||
use crate::unit_client::UnitClientError;
|
||||
use crate::unitd_process::UnitdProcess;
|
||||
use crate::control_socket_address::ControlSocket;
|
||||
|
||||
use bollard::container::{Config, ListContainersOptions, StartContainerOptions};
|
||||
use bollard::image::CreateImageOptions;
|
||||
use bollard::models::{
|
||||
ContainerCreateResponse, HostConfig, Mount,
|
||||
MountTypeEnum, ContainerSummary,
|
||||
};
|
||||
use bollard::models::{ContainerCreateResponse, ContainerSummary, HostConfig, Mount, MountTypeEnum};
|
||||
use bollard::secret::ContainerInspectResponse;
|
||||
use bollard::Docker;
|
||||
|
||||
|
@ -156,16 +153,12 @@ impl UnitdContainer {
|
|||
// cant do this functionally because of the async call
|
||||
let mut mapped = vec![];
|
||||
for ctr in summary {
|
||||
if unitd_command_re.is_match(&ctr.clone().command
|
||||
.or(Some(String::new()))
|
||||
.unwrap()) {
|
||||
if unitd_command_re.is_match(&ctr.clone().command.or(Some(String::new())).unwrap()) {
|
||||
let mut c = UnitdContainer::from(&ctr);
|
||||
if let Some(names) = ctr.names {
|
||||
if names.len() > 0 {
|
||||
let name = names[0].strip_prefix("/")
|
||||
.or(Some(names[0].as_str())).unwrap();
|
||||
if let Ok(cir) = docker
|
||||
.inspect_container(name, None).await {
|
||||
let name = names[0].strip_prefix("/").or(Some(names[0].as_str())).unwrap();
|
||||
if let Ok(cir) = docker.inspect_container(name, None).await {
|
||||
c.details = Some(cir);
|
||||
}
|
||||
}
|
||||
|
@ -196,11 +189,11 @@ impl UnitdContainer {
|
|||
|
||||
// either return translated path or original prefixed with "container"
|
||||
if keys.len() > 0 {
|
||||
let mut matches = self.mounts[&keys[0]]
|
||||
.clone()
|
||||
.join(cp.as_path()
|
||||
let mut matches = self.mounts[&keys[0]].clone().join(
|
||||
cp.as_path()
|
||||
.strip_prefix(keys[0].clone())
|
||||
.expect("error checking path prefix"));
|
||||
.expect("error checking path prefix"),
|
||||
);
|
||||
/* Observed on M1 Mac that Docker on OSX
|
||||
* adds a bunch of garbage to the mount path
|
||||
* converting it into a useless directory
|
||||
|
@ -208,15 +201,14 @@ impl UnitdContainer {
|
|||
*/
|
||||
if cfg!(target_os = "macos") {
|
||||
let mut abs = PathBuf::from(String::from(MAIN_SEPARATOR));
|
||||
let m = matches.strip_prefix("/host_mnt/private")
|
||||
.unwrap_or(matches.strip_prefix("/host_mnt")
|
||||
.unwrap_or(matches.as_path()));
|
||||
let m = matches
|
||||
.strip_prefix("/host_mnt/private")
|
||||
.unwrap_or(matches.strip_prefix("/host_mnt").unwrap_or(matches.as_path()));
|
||||
// make it absolute again
|
||||
abs.push(m);
|
||||
matches = abs;
|
||||
}
|
||||
matches.to_string_lossy()
|
||||
.to_string()
|
||||
matches.to_string_lossy().to_string()
|
||||
} else {
|
||||
format!("<container>:{}", cp.display())
|
||||
}
|
||||
|
@ -264,10 +256,7 @@ pub async fn deploy_new_container(
|
|||
let mut mounts = vec![];
|
||||
// if a unix socket is specified, mounts its directory
|
||||
if socket.is_local_socket() {
|
||||
let mount_path = PathBuf::from(socket.clone())
|
||||
.as_path()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let mount_path = PathBuf::from(socket.clone()).as_path().to_string_lossy().to_string();
|
||||
mounts.push(Mount {
|
||||
typ: Some(MountTypeEnum::BIND),
|
||||
source: Some(mount_path),
|
||||
|
@ -290,19 +279,19 @@ pub async fn deploy_new_container(
|
|||
Some(CreateImageOptions {
|
||||
from_image: image.as_str(),
|
||||
..Default::default()
|
||||
}), None, None
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
while let Some(res) = stream.next().await {
|
||||
if let Ok(info) = res {
|
||||
if let Some(id) = info.id {
|
||||
if let Some(_) = totals.get_mut(&id) {
|
||||
if let Some(delta) = info.progress_detail
|
||||
.and_then(|detail| detail.current) {
|
||||
if let Some(delta) = info.progress_detail.and_then(|detail| detail.current) {
|
||||
pb.add(delta as u64);
|
||||
}
|
||||
} else {
|
||||
if let Some(total) = info.progress_detail
|
||||
.and_then(|detail| detail.total) {
|
||||
if let Some(total) = info.progress_detail.and_then(|detail| detail.total) {
|
||||
totals.insert(id, total);
|
||||
pb.total += total as u64;
|
||||
}
|
||||
|
@ -357,27 +346,27 @@ pub async fn deploy_new_container(
|
|||
.await
|
||||
{
|
||||
// somehow our container doesnt exist
|
||||
Err(e) => Err(UnitClientError::UnitdDockerError{
|
||||
message: e.to_string()
|
||||
}),
|
||||
Err(e) => Err(UnitClientError::UnitdDockerError { message: e.to_string() }),
|
||||
// here it is!
|
||||
Ok(info) => {
|
||||
if info.len() < 1 {
|
||||
return Err(UnitClientError::UnitdDockerError {
|
||||
message: "couldnt find new container".to_string(),
|
||||
});
|
||||
} else if info[0].names.is_none() ||
|
||||
info[0].names.clone().unwrap().len() < 1 {
|
||||
} else if info[0].names.is_none() || info[0].names.clone().unwrap().len() < 1 {
|
||||
return Err(UnitClientError::UnitdDockerError {
|
||||
message: "new container has no name".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// start our container
|
||||
match docker.start_container(
|
||||
match docker
|
||||
.start_container(
|
||||
info[0].names.clone().unwrap()[0].strip_prefix(MAIN_SEPARATOR).unwrap(),
|
||||
None::<StartContainerOptions<String>>,
|
||||
).await {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(err) => Err(UnitClientError::UnitdDockerError {
|
||||
message: err.to_string(),
|
||||
}),
|
||||
|
@ -439,14 +428,8 @@ mod tests {
|
|||
ctr.host_path("/path/to/conf".to_string())
|
||||
);
|
||||
if cfg!(target_os = "macos") {
|
||||
assert_eq!(
|
||||
"/6/test".to_string(),
|
||||
ctr.host_path("/var/test".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
"/7/test".to_string(),
|
||||
ctr.host_path("/var/var/test".to_string())
|
||||
);
|
||||
assert_eq!("/6/test".to_string(), ctr.host_path("/var/test".to_string()));
|
||||
assert_eq!("/7/test".to_string(), ctr.host_path("/var/var/test".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,21 +26,18 @@ docs/ConfigApplicationCommonLimits.md
|
|||
docs/ConfigApplicationCommonProcesses.md
|
||||
docs/ConfigApplicationCommonProcessesAnyOf.md
|
||||
docs/ConfigApplicationExternal.md
|
||||
docs/ConfigApplicationExternalAllOf.md
|
||||
docs/ConfigApplicationJava.md
|
||||
docs/ConfigApplicationJavaAllOf.md
|
||||
docs/ConfigApplicationPerl.md
|
||||
docs/ConfigApplicationPerlAllOf.md
|
||||
docs/ConfigApplicationPhp.md
|
||||
docs/ConfigApplicationPhpAllOf.md
|
||||
docs/ConfigApplicationPhpAllOfOptions.md
|
||||
docs/ConfigApplicationPhpAllOfTargets.md
|
||||
docs/ConfigApplicationPython.md
|
||||
docs/ConfigApplicationPythonAllOf.md
|
||||
docs/ConfigApplicationPythonAllOfPath.md
|
||||
docs/ConfigApplicationPythonAllOfTargets.md
|
||||
docs/ConfigApplicationRuby.md
|
||||
docs/ConfigApplicationRubyAllOf.md
|
||||
docs/ConfigApplicationWasi.md
|
||||
docs/ConfigApplicationWasm.md
|
||||
docs/ConfigApplicationWasmAllOfAccess.md
|
||||
docs/ConfigListener.md
|
||||
docs/ConfigListenerForwarded.md
|
||||
docs/ConfigListenerForwardedSource.md
|
||||
|
@ -114,21 +111,18 @@ src/models/config_application_common_limits.rs
|
|||
src/models/config_application_common_processes.rs
|
||||
src/models/config_application_common_processes_any_of.rs
|
||||
src/models/config_application_external.rs
|
||||
src/models/config_application_external_all_of.rs
|
||||
src/models/config_application_java.rs
|
||||
src/models/config_application_java_all_of.rs
|
||||
src/models/config_application_perl.rs
|
||||
src/models/config_application_perl_all_of.rs
|
||||
src/models/config_application_php.rs
|
||||
src/models/config_application_php_all_of.rs
|
||||
src/models/config_application_php_all_of_options.rs
|
||||
src/models/config_application_php_all_of_targets.rs
|
||||
src/models/config_application_python.rs
|
||||
src/models/config_application_python_all_of.rs
|
||||
src/models/config_application_python_all_of_path.rs
|
||||
src/models/config_application_python_all_of_targets.rs
|
||||
src/models/config_application_ruby.rs
|
||||
src/models/config_application_ruby_all_of.rs
|
||||
src/models/config_application_wasi.rs
|
||||
src/models/config_application_wasm.rs
|
||||
src/models/config_application_wasm_all_of_access.rs
|
||||
src/models/config_listener.rs
|
||||
src/models/config_listener_forwarded.rs
|
||||
src/models/config_listener_forwarded_source.rs
|
||||
|
|
|
@ -1 +1 @@
|
|||
6.6.0
|
||||
7.6.0
|
||||
|
|
|
@ -22,6 +22,7 @@ This API client was generated by the [OpenAPI Generator](https://openapi-generat
|
|||
|
||||
- API version: 0.2.0
|
||||
- Package version: 0.4.0-beta
|
||||
- Generator version: 7.6.0
|
||||
- Build package: `org.openapitools.codegen.languages.RustClientCodegen`
|
||||
|
||||
## Installation
|
||||
|
@ -354,21 +355,18 @@ Class | Method | HTTP request | Description
|
|||
- [ConfigApplicationCommonProcesses](docs/ConfigApplicationCommonProcesses.md)
|
||||
- [ConfigApplicationCommonProcessesAnyOf](docs/ConfigApplicationCommonProcessesAnyOf.md)
|
||||
- [ConfigApplicationExternal](docs/ConfigApplicationExternal.md)
|
||||
- [ConfigApplicationExternalAllOf](docs/ConfigApplicationExternalAllOf.md)
|
||||
- [ConfigApplicationJava](docs/ConfigApplicationJava.md)
|
||||
- [ConfigApplicationJavaAllOf](docs/ConfigApplicationJavaAllOf.md)
|
||||
- [ConfigApplicationPerl](docs/ConfigApplicationPerl.md)
|
||||
- [ConfigApplicationPerlAllOf](docs/ConfigApplicationPerlAllOf.md)
|
||||
- [ConfigApplicationPhp](docs/ConfigApplicationPhp.md)
|
||||
- [ConfigApplicationPhpAllOf](docs/ConfigApplicationPhpAllOf.md)
|
||||
- [ConfigApplicationPhpAllOfOptions](docs/ConfigApplicationPhpAllOfOptions.md)
|
||||
- [ConfigApplicationPhpAllOfTargets](docs/ConfigApplicationPhpAllOfTargets.md)
|
||||
- [ConfigApplicationPython](docs/ConfigApplicationPython.md)
|
||||
- [ConfigApplicationPythonAllOf](docs/ConfigApplicationPythonAllOf.md)
|
||||
- [ConfigApplicationPythonAllOfPath](docs/ConfigApplicationPythonAllOfPath.md)
|
||||
- [ConfigApplicationPythonAllOfTargets](docs/ConfigApplicationPythonAllOfTargets.md)
|
||||
- [ConfigApplicationRuby](docs/ConfigApplicationRuby.md)
|
||||
- [ConfigApplicationRubyAllOf](docs/ConfigApplicationRubyAllOf.md)
|
||||
- [ConfigApplicationWasi](docs/ConfigApplicationWasi.md)
|
||||
- [ConfigApplicationWasm](docs/ConfigApplicationWasm.md)
|
||||
- [ConfigApplicationWasmAllOfAccess](docs/ConfigApplicationWasmAllOfAccess.md)
|
||||
- [ConfigListener](docs/ConfigListener.md)
|
||||
- [ConfigListenerForwarded](docs/ConfigListenerForwarded.md)
|
||||
- [ConfigListenerForwardedSource](docs/ConfigListenerForwardedSource.md)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#![allow(clippy::all)]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#![allow(unused_imports)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
extern crate futures;
|
||||
extern crate hyper;
|
||||
|
|
36
tools/unitctl/unitctl/src/cmd/applications.rs
Normal file
36
tools/unitctl/unitctl/src/cmd/applications.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use crate::unitctl::{ApplicationArgs, ApplicationCommands, UnitCtl};
|
||||
use crate::{wait, UnitctlError};
|
||||
use crate::requests::send_empty_body_deserialize_response;
|
||||
use unit_client_rs::unit_client::UnitClient;
|
||||
|
||||
pub(crate) async fn cmd(cli: &UnitCtl, args: &ApplicationArgs) -> Result<(), UnitctlError> {
|
||||
let control_socket = wait::wait_for_socket(cli).await?;
|
||||
let client = UnitClient::new(control_socket);
|
||||
|
||||
match &args.command {
|
||||
ApplicationCommands::Reload { ref name } => client
|
||||
.restart_application(name)
|
||||
.await
|
||||
.map_err(|e| UnitctlError::UnitClientError { source: *e })
|
||||
.and_then(|r| args.output_format.write_to_stdout(&r)),
|
||||
|
||||
/* we should be able to use this but the openapi generator library
|
||||
* is fundamentally incorrect and provides a broken API for the
|
||||
* applications endpoint.
|
||||
ApplicationCommands::List {} => client
|
||||
.applications()
|
||||
.await
|
||||
.map_err(|e| UnitctlError::UnitClientError { source: *e })
|
||||
.and_then(|response| args.output_format.write_to_stdout(&response)),*/
|
||||
|
||||
ApplicationCommands::List {} => {
|
||||
args.output_format.write_to_stdout(
|
||||
&send_empty_body_deserialize_response(
|
||||
&client,
|
||||
"GET",
|
||||
"/config/applications",
|
||||
).await?
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use crate::unitctl::{InstanceArgs, InstanceCommands};
|
||||
use crate::{OutputFormat, UnitctlError};
|
||||
use crate::unitctl_error::ControlSocketErrorKind;
|
||||
use crate::{OutputFormat, UnitctlError};
|
||||
|
||||
use std::path::PathBuf;
|
||||
use unit_client_rs::control_socket_address::ControlSocket;
|
||||
use unit_client_rs::unitd_docker::deploy_new_container;
|
||||
use unit_client_rs::unitd_instance::UnitdInstance;
|
||||
use unit_client_rs::control_socket_address::ControlSocket;
|
||||
|
||||
pub(crate) async fn cmd(args: InstanceArgs) -> Result<(), UnitctlError> {
|
||||
if let Some(cmd) = args.command {
|
||||
|
@ -22,37 +22,38 @@ pub(crate) async fn cmd(args: InstanceArgs) -> Result<(), UnitctlError> {
|
|||
} else if !PathBuf::from(application).as_path().exists() {
|
||||
eprintln!("application path must exist");
|
||||
Err(UnitctlError::NoFilesImported)
|
||||
|
||||
} else {
|
||||
let addr = ControlSocket::parse_address(socket);
|
||||
if let Err(e) = addr {
|
||||
return Err(UnitctlError::UnitClientError{source: e});
|
||||
return Err(UnitctlError::UnitClientError { source: e });
|
||||
}
|
||||
|
||||
// validate we arent processing an abstract socket
|
||||
if let ControlSocket::UnixLocalAbstractSocket(_) = addr.as_ref().unwrap() {
|
||||
return Err(UnitctlError::ControlSocketError{
|
||||
return Err(UnitctlError::ControlSocketError {
|
||||
kind: ControlSocketErrorKind::General,
|
||||
message: "cannot pass abstract socket to docker container".to_string(),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// warn user of OSX docker limitations
|
||||
if let ControlSocket::UnixLocalSocket(ref sock_path) = addr.as_ref().unwrap() {
|
||||
if cfg!(target_os = "macos") {
|
||||
return Err(UnitctlError::ControlSocketError{
|
||||
return Err(UnitctlError::ControlSocketError {
|
||||
kind: ControlSocketErrorKind::General,
|
||||
message: format!("Docker on OSX will break unix sockets mounted {} {}",
|
||||
message: format!(
|
||||
"Docker on macOS will break unix domain sockets mounted {} {}",
|
||||
"in containers, see the following link for more information",
|
||||
"https://github.com/docker/for-mac/issues/483"),
|
||||
})
|
||||
"https://github.com/docker/for-mac/issues/483"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if !sock_path.is_dir() {
|
||||
return Err(UnitctlError::ControlSocketError{
|
||||
return Err(UnitctlError::ControlSocketError {
|
||||
kind: ControlSocketErrorKind::General,
|
||||
message: "user must specify a directory of UNIX socket directory".to_string(),
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,28 +61,30 @@ pub(crate) async fn cmd(args: InstanceArgs) -> Result<(), UnitctlError> {
|
|||
if let ControlSocket::TcpSocket(uri) = addr.as_ref().unwrap() {
|
||||
if let Some(host) = uri.host() {
|
||||
if host != "127.0.0.1" {
|
||||
return Err(UnitctlError::ControlSocketError{
|
||||
return Err(UnitctlError::ControlSocketError {
|
||||
kind: ControlSocketErrorKind::General,
|
||||
message: "TCP URI must point to 127.0.0.1".to_string(),
|
||||
})
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Err(UnitctlError::ControlSocketError{
|
||||
return Err(UnitctlError::ControlSocketError {
|
||||
kind: ControlSocketErrorKind::General,
|
||||
message: "TCP URI must point to a host".to_string(),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(port) = uri.port_u16() {
|
||||
if port < 1025 {
|
||||
eprintln!("warning! you are asking docker to forward a privileged port. {}",
|
||||
"please make sure docker has access to it");
|
||||
eprintln!(
|
||||
"warning! you are asking docker to forward a privileged port. {}",
|
||||
"please make sure docker has access to it"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return Err(UnitctlError::ControlSocketError{
|
||||
return Err(UnitctlError::ControlSocketError {
|
||||
kind: ControlSocketErrorKind::General,
|
||||
message: "TCP URI must specify a port".to_string(),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if uri.path() != "/" {
|
||||
|
@ -95,11 +98,13 @@ pub(crate) async fn cmd(args: InstanceArgs) -> Result<(), UnitctlError> {
|
|||
eprintln!("> Will READ ONLY mount {} to /www for application access", application);
|
||||
eprintln!("> Container will be on host network");
|
||||
match addr.as_ref().unwrap() {
|
||||
ControlSocket::UnixLocalSocket(path) =>
|
||||
eprintln!("> Will mount directory containing {} to /var/www for control API",
|
||||
path.as_path().to_string_lossy()),
|
||||
ControlSocket::TcpSocket(uri) =>
|
||||
eprintln!("> Will forward port {} for control API", uri.port_u16().unwrap()),
|
||||
ControlSocket::UnixLocalSocket(path) => eprintln!(
|
||||
"> Will mount directory containing {} to /var/www for control API",
|
||||
path.as_path().to_string_lossy()
|
||||
),
|
||||
ControlSocket::TcpSocket(uri) => {
|
||||
eprintln!("> Will forward port {} for control API", uri.port_u16().unwrap())
|
||||
}
|
||||
_ => unimplemented!(), // abstract socket case ruled out previously
|
||||
}
|
||||
|
||||
|
@ -108,7 +113,9 @@ pub(crate) async fn cmd(args: InstanceArgs) -> Result<(), UnitctlError> {
|
|||
}
|
||||
|
||||
// do the actual deployment
|
||||
deploy_new_container(addr.unwrap(), application, image).await.map_or_else(
|
||||
deploy_new_container(addr.unwrap(), application, image)
|
||||
.await
|
||||
.map_or_else(
|
||||
|e| Err(UnitctlError::UnitClientError { source: e }),
|
||||
|warn| {
|
||||
for i in warn {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub(crate) mod applications;
|
||||
pub(crate) mod edit;
|
||||
pub(crate) mod execute;
|
||||
pub(crate) mod import;
|
||||
|
|
|
@ -8,7 +8,7 @@ extern crate unit_client_rs;
|
|||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::cmd::{edit, execute as execute_cmd, import, instances, listeners, status};
|
||||
use crate::cmd::{applications, edit, execute as execute_cmd, import, instances, listeners, status};
|
||||
use crate::output_format::OutputFormat;
|
||||
use crate::unitctl::{Commands, UnitCtl};
|
||||
use crate::unitctl_error::UnitctlError;
|
||||
|
@ -30,6 +30,8 @@ async fn main() -> Result<(), UnitctlError> {
|
|||
match cli.command {
|
||||
Commands::Instances(args) => instances::cmd(args).await,
|
||||
|
||||
Commands::App(ref args) => applications::cmd(&cli, args).await,
|
||||
|
||||
Commands::Edit { output_format } => edit::cmd(&cli, output_format).await,
|
||||
|
||||
Commands::Import { ref directory } => import::cmd(&cli, directory).await,
|
||||
|
@ -67,6 +69,9 @@ fn eprint_error(error: &UnitctlError) {
|
|||
eprintln!("{}", source);
|
||||
eprintln!("Try running again with the same permissions as the unit control socket");
|
||||
}
|
||||
UnitClientError::OpenAPIError { source } => {
|
||||
eprintln!("OpenAPI Error: {}", source);
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Unit client error: {}", source);
|
||||
}
|
||||
|
|
|
@ -41,9 +41,9 @@ pub(crate) struct UnitCtl {
|
|||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
#[command(about = "List all running UNIT processes")]
|
||||
#[command(about = "List all running Unit processes")]
|
||||
Instances(InstanceArgs),
|
||||
#[command(about = "Open current UNIT configuration in editor")]
|
||||
#[command(about = "Open current Unit configuration in editor")]
|
||||
Edit {
|
||||
#[arg(
|
||||
required = false,
|
||||
|
@ -60,7 +60,7 @@ pub(crate) enum Commands {
|
|||
#[arg(required = true, help = "Directory to import from")]
|
||||
directory: PathBuf,
|
||||
},
|
||||
#[command(about = "Sends raw JSON payload to UNIT")]
|
||||
#[command(about = "Sends raw JSON payload to Unit")]
|
||||
Execute {
|
||||
#[arg(
|
||||
required = false,
|
||||
|
@ -90,7 +90,7 @@ pub(crate) enum Commands {
|
|||
#[arg(required = true, short = 'p', long = "path")]
|
||||
path: String,
|
||||
},
|
||||
#[command(about = "Get the current status of UNIT")]
|
||||
#[command(about = "Get the current status of Unit")]
|
||||
Status {
|
||||
#[arg(
|
||||
required = false,
|
||||
|
@ -114,6 +114,8 @@ pub(crate) enum Commands {
|
|||
)]
|
||||
output_format: OutputFormat,
|
||||
},
|
||||
#[command(about = "List all configured Unit applications")]
|
||||
App(ApplicationArgs),
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
|
@ -135,7 +137,7 @@ pub struct InstanceArgs {
|
|||
#[derive(Debug, Subcommand)]
|
||||
#[command(args_conflicts_with_subcommands = true)]
|
||||
pub enum InstanceCommands {
|
||||
#[command(about = "deploy a new docker instance of unitd")]
|
||||
#[command(about = "deploy a new docker instance of Unit")]
|
||||
New {
|
||||
#[arg(required = true, help = "Path to mount control socket to host")]
|
||||
socket: String,
|
||||
|
@ -151,6 +153,35 @@ pub enum InstanceCommands {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct ApplicationArgs {
|
||||
#[arg(
|
||||
required = false,
|
||||
global = true,
|
||||
short = 't',
|
||||
long = "output-format",
|
||||
default_value = "text",
|
||||
help = "Output format: text, yaml, json, json-pretty (default)"
|
||||
)]
|
||||
pub output_format: OutputFormat,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub command: ApplicationCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
#[command(args_conflicts_with_subcommands = true)]
|
||||
pub enum ApplicationCommands {
|
||||
#[command(about = "reload a running application")]
|
||||
Reload {
|
||||
#[arg(required = true, help = "name of application")]
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[command(about = "list running applications")]
|
||||
List {},
|
||||
}
|
||||
|
||||
fn parse_control_socket_address(s: &str) -> Result<ControlSocket, ClapError> {
|
||||
ControlSocket::try_from(s).map_err(|e| ClapError::raw(ValueValidation, e.to_string()))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue