Skip to content

Commit

Permalink
Reusable Worker Service (#286)
Browse files Browse the repository at this point in the history
* initial worker service with auth

* integrate worker connect

* add template id to permission

* format and clippy

* delete old worker service

* clippy and fmt

* WorkerServiceBaseError to WorkerServiceError

* remove golem wit

* propagate namespace in return

* update openapi yaml

* use WithNamespace instead of tuple

* remove annotated in favor of WithNamespace

* replace anyhow::Error::msg with Error::new

* handle new golem-client cases
  • Loading branch information
nicoburniske authored Mar 21, 2024
1 parent 123f15b commit 1a07e54
Show file tree
Hide file tree
Showing 26 changed files with 1,894 additions and 1,455 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions golem-cli/src/clients/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ impl ResponseContentErrorMapper for WorkerError {
fn map(self) -> String {
match self {
WorkerError::Error400(errors) => errors.errors.iter().join(", "),
WorkerError::Error401(error) => error.error,
WorkerError::Error403(error) => error.error,
WorkerError::Error404(error) => error.error,
WorkerError::Error409(error) => error.error,
WorkerError::Error500(error) => display_golem_error(error.golem_error),
Expand Down
1 change: 1 addition & 0 deletions golem-service-base/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ tokio = { workspace = true }
tonic = { workspace = true }
tracing = { workspace = true }
url = { workspace = true }
thiserror = { workspace = true }

[dev-dependencies]
proptest = { workspace = true }
52 changes: 52 additions & 0 deletions golem-service-base/src/service/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use async_trait::async_trait;
use serde::{Deserialize, Serialize};

// Every authorisation is based on a permission to a particular context.
// A context can be a simple unit, to a user, namespace, project, account, or
// a mere request from where we can fetch details.
#[async_trait]
pub trait AuthService<AuthCtx, Namespace, P = Permission> {
async fn is_authorized(&self, permission: P, ctx: &AuthCtx) -> Result<Namespace, AuthError>;
}

#[derive(Debug, Clone, thiserror::Error)]
pub enum AuthError {
// TODO: Do we want to display these errors?
#[error("Unauthorized: {0}")]
Unauthorized(String),
#[error("Forbidden: {0}")]
Forbidden(String),
#[error("Internal error: {0}")]
Internal(String),
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Permission {
View,
Create,
Update,
Delete,
}

impl std::fmt::Display for Permission {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Permission::View => write!(f, "View"),
Permission::Create => write!(f, "Create"),
Permission::Update => write!(f, "Update"),
Permission::Delete => write!(f, "Delete"),
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct WithNamespace<T, Namespace> {
pub value: T,
pub namespace: Namespace,
}

impl<T, Namespace> WithNamespace<T, Namespace> {
pub fn new(value: T, namespace: Namespace) -> Self {
Self { value, namespace }
}
}
1 change: 1 addition & 0 deletions golem-service-base/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
// See the License for the specific language governing permissions and
// limitations under the License.

pub mod auth;
pub mod template_object_store;
1 change: 1 addition & 0 deletions golem-worker-service-base/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ opentelemetry-prometheus = { workspace = true }
futures-util = { workspace = true }
tap = "1.0.1"
thiserror = { workspace = true }
anyhow = { workspace = true }
5 changes: 4 additions & 1 deletion golem-worker-service-base/src/api/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ impl ApiEndpointError {
}

mod conversion {
use golem_service_base::service::auth::AuthError;
use poem_openapi::payload::Json;

use super::{
ApiEndpointError, RouteValidationError, ValidationErrorsBody, WorkerServiceErrorsBody,
};
use crate::api_definition_repo::ApiRegistrationRepoError;
use crate::auth::AuthError;
use crate::service::api_definition::ApiRegistrationError;
use crate::service::api_definition_validator::ValidationError;

Expand All @@ -116,6 +116,9 @@ mod conversion {
ApiRegistrationError::AuthenticationError(AuthError::Unauthorized { .. }) => {
ApiEndpointError::unauthorized(error)
}
ApiRegistrationError::AuthenticationError(AuthError::Internal(_)) => {
ApiEndpointError::internal("Internal error")
}
ApiRegistrationError::RepoError(ApiRegistrationRepoError::AlreadyExists(_)) => {
ApiEndpointError::already_exists(error)
}
Expand Down
57 changes: 36 additions & 21 deletions golem-worker-service-base/src/api/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::service::error::TemplateServiceBaseError;
use crate::service::error::TemplateServiceError;
use crate::service::worker::WorkerServiceError;
use golem_service_base::model::*;
use poem_openapi::payload::Json;
use poem_openapi::*;
Expand All @@ -14,6 +15,10 @@ use tonic::Status;
pub enum WorkerApiBaseError {
#[oai(status = 400)]
BadRequest(Json<ErrorsBody>),
#[oai(status = 401)]
Unauthorized(Json<ErrorBody>),
#[oai(status = 403)]
Forbidden(Json<ErrorBody>),
#[oai(status = 404)]
NotFound(Json<ErrorBody>),
#[oai(status = 409)]
Expand Down Expand Up @@ -50,21 +55,31 @@ impl From<String> for WorkerApiBaseError {
}
}

impl From<crate::service::error::WorkerServiceBaseError> for WorkerApiBaseError {
fn from(value: crate::service::error::WorkerServiceBaseError) -> Self {
use crate::service::error::WorkerServiceBaseError as ServiceError;
impl From<WorkerServiceError> for WorkerApiBaseError {
fn from(value: WorkerServiceError) -> Self {
use golem_service_base::service::auth::AuthError;
use WorkerServiceError as ServiceError;

fn internal(details: String) -> WorkerApiBaseError {
WorkerApiBaseError::InternalError(Json(GolemErrorBody {
golem_error: GolemError::Unknown(GolemErrorUnknown { details }),
}))
}

match value {
ServiceError::Internal(error) => {
WorkerApiBaseError::InternalError(Json(GolemErrorBody {
golem_error: GolemError::Unknown(GolemErrorUnknown { details: error }),
}))
}
ServiceError::TypeCheckerError(error) => {
WorkerApiBaseError::BadRequest(Json(ErrorsBody {
errors: vec![format!("Type checker error: {error}")],
}))
}
ServiceError::Auth(error) => match error {
AuthError::Unauthorized(error) => {
WorkerApiBaseError::Unauthorized(Json(ErrorBody { error }))
}
AuthError::Forbidden(error) => {
WorkerApiBaseError::Forbidden(Json(ErrorBody { error }))
}
AuthError::Internal(error) => internal(error.to_string()),
},
ServiceError::Internal(error) => internal(error.to_string()),
ServiceError::TypeChecker(error) => WorkerApiBaseError::BadRequest(Json(ErrorsBody {
errors: vec![format!("Type checker error: {error}")],
})),
ServiceError::VersionedTemplateIdNotFound(template_id) => {
WorkerApiBaseError::NotFound(Json(ErrorBody {
error: format!("Template not found: {template_id}"),
Expand All @@ -88,28 +103,28 @@ impl From<crate::service::error::WorkerServiceBaseError> for WorkerApiBaseError
ServiceError::Golem(golem_error) => {
WorkerApiBaseError::InternalError(Json(GolemErrorBody { golem_error }))
}
ServiceError::DelegatedTemplateServiceError(error) => error.into(),
ServiceError::Template(error) => error.into(),
}
}
}

impl From<TemplateServiceBaseError> for WorkerApiBaseError {
fn from(value: TemplateServiceBaseError) -> Self {
impl From<TemplateServiceError> for WorkerApiBaseError {
fn from(value: TemplateServiceError) -> Self {
match value {
TemplateServiceBaseError::Connection(error) => WorkerApiBaseError::InternalError(Json(GolemErrorBody {
TemplateServiceError::Connection(error) => WorkerApiBaseError::InternalError(Json(GolemErrorBody {
golem_error: GolemError::Unknown(GolemErrorUnknown { details: format!("Internal connection error: {error}") }),
})),
TemplateServiceBaseError::Other(error) => {
TemplateServiceError::Internal(error) => {
WorkerApiBaseError::InternalError(Json(GolemErrorBody {
golem_error: GolemError::Unknown(GolemErrorUnknown { details: format!("Internal error: {error}") }),
}))
},
TemplateServiceBaseError::Transport(_) => {
TemplateServiceError::Transport(_) => {
WorkerApiBaseError::InternalError(Json(GolemErrorBody {
golem_error: GolemError::Unknown(GolemErrorUnknown { details: "Transport Error when connecting to template service".to_string() }),
}))
},
TemplateServiceBaseError::Server(template_error) => {
TemplateServiceError::Server(template_error) => {
match template_error.error {
Some(error) => match error {
golem_api_grpc::proto::golem::template::template_error::Error::BadRequest(errors) => {
Expand Down
39 changes: 3 additions & 36 deletions golem-worker-service-base/src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,14 @@
use std::fmt::{Display, Formatter};

use async_trait::async_trait;
use derive_more::Display;
use serde::{Deserialize, Serialize};

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Display)]
pub enum Permission {
View,
Create,
Update,
Delete,
}

// Every authorisation is based on a permission to a particular context.
// A context can be a simple unit, to a user, namespace, project, account, or
// a mere request from where we can fetch details.
#[async_trait]
pub trait AuthService<AuthCtx, Namespace> {
async fn is_authorized(
&self,
permission: Permission,
ctx: &AuthCtx,
) -> Result<Namespace, AuthError>;
}

#[derive(Debug, Clone, thiserror::Error)]
pub enum AuthError {
#[error("Unauthorized: {0}")]
Unauthorized(String),
#[error("Auth {permission} is forbidden: {reason}")]
Forbidden {
permission: Permission,
reason: String,
},
}
use golem_service_base::service::auth::{AuthError, AuthService, Permission};
use serde::Deserialize;

pub struct AuthServiceNoop {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EmptyAuthCtx {}

#[derive(
Debug, Clone, PartialEq, Eq, Hash, bincode::Encode, bincode::Decode, serde::Deserialize,
)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, bincode::Encode, bincode::Decode, Deserialize)]
pub struct CommonNamespace(String);

impl Default for CommonNamespace {
Expand Down
Loading

0 comments on commit 1a07e54

Please sign in to comment.