Skip to content

Commit

Permalink
Artifact Submission
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinbootz committed Jun 30, 2022
1 parent f509773 commit e55652f
Show file tree
Hide file tree
Showing 23 changed files with 1,011 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ jobs:
name: Build and Test
uses: Apodini/.github/.github/workflows/build-and-test.yml@v1
with:
packagename: ApodiniTemplate
packagename: ApodiniSustainability
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
name: Build and Test
uses: Apodini/.github/.github/workflows/build-and-test.yml@v1
with:
packagename: ApodiniTemplate
packagename: ApodiniSustainability
reuse_action:
name: REUSE Compliance Check
uses: Apodini/.github/.github/workflows/reuse.yml@v1
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ jobs:
name: Generate Docs
uses: Apodini/.github/.github/workflows/docs.yml@v1
with:
packagename: ApodiniTemplate
targetname: ApodiniSustainability
53 changes: 36 additions & 17 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,31 +1,50 @@
// swift-tools-version:5.5

//
// This source file is part of the Apodini open source project
//
// SPDX-FileCopyrightText: 2021 Paul Schmiedmayer and the project authors (see CONTRIBUTORS.md) <paul.schmiedmayer@tum.de>
//
// SPDX-License-Identifier: MIT
//
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription


let package = Package(
name: "ApodiniTemplate",
name: "ApodiniSustainability",
platforms: [
.macOS(.v11)
.macOS(.v12)
],
products: [
.library(name: "ApodiniTemplate", targets: ["ApodiniTemplate"])
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "ApodiniSustainability",
targets: ["ApodiniSustainability"]),
.executable(
name: "ApodiniSustainabilityDemo",
targets: ["ApodiniSustainabilityDemo"])
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/Apodini/Apodini.git", .upToNextMinor(from: "0.9.0")),
.package(url: "https://github.com/Apodini/ApodiniDocumentExport.git", .upToNextMinor(from: "0.1.0")),
],
targets: [
.target(name: "ApodiniTemplate"),
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "ApodiniSustainability",
dependencies: [
.product(name: "Apodini", package: "Apodini"),
.product(name: "ApodiniNetworking", package: "Apodini"),
.product(name: "ApodiniDocumentExport", package: "ApodiniDocumentExport")
]),
.executableTarget(
name: "ApodiniSustainabilityDemo",
dependencies: [
.product(name: "Apodini", package: "Apodini"),
.product(name: "ApodiniREST", package: "Apodini"),
.target(name: "ApodiniSustainability")
]),
.testTarget(
name: "ApodiniTemplateTests",
name: "ApodiniSustainabilityTests",
dependencies: [
.target(name: "ApodiniTemplate")
]
)
.target(name: "ApodiniSustainability"),
.product(name: "XCTApodiniNetworking", package: "Apodini")
]),
]
)
82 changes: 54 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,62 @@ SPDX-License-Identifier: MIT
-->

## How to use this repository
### Template

When creating a new repository, make sure to select this repository as a repository template.

### Customize the repository

Enter your repository-specific configuration
- Replace the "Package.swift", "Sources" and "Tests" folder with your Swift Package
- Enter the correct Swift Package name (currently "ApodiniTemplate") in the build.yml, pull_request.yml and release.yml files.
- Update the DocC documentation to reflect the name of the new Swift package and adapt the docs and build and test GitHub Actions where the documentation is generated to the updated names to be sure the DocC generation works as expected
- Update the README with your information and replace the links to the license with the new repository.
- Update the status badges to point to the GitHub actions of your repository.
- If you create a new repository in the Apodini organization, you do not need to add a personal access token named "ACCESS_TOKEN". If you create the repo outside the Apodini organization, you need to create such a token with write access to the repo for all GitHub Actions to work. You will need to give the `ApodiniBot` user write access to the repository.

### ⬆️ Remove everything up to here ⬆️

# Project Name

[![Build](https://github.com/Apodini/Template-Repository/actions/workflows/build.yml/badge.svg)](https://github.com/Apodini/Template-Repository/actions/workflows/build.yml)
[![codecov](https://codecov.io/gh/Apodini/Template-Repository/branch/develop/graph/badge.svg?token=5MMKMPO5NR)](https://codecov.io/gh/Apodini/Template-Repository)

## Requirements

## Installation/Setup/Integration

## Usage
# Apodini Sustainability

[![Build](https://github.com/Apodini/ApodiniSustainability/actions/workflows/build.yml/badge.svg)](https://github.com/Apodini/ApodiniSustainability/actions/workflows/build.yml)
[![codecov](https://codecov.io/gh/Apodini/ApodiniSustainability/branch/develop/graph/badge.svg?token=5MMKMPO5NR)](https://codecov.io/gh/Apodini/ApodiniSustainability)

ApodiniSustainability includes metadata definitions for annotating Apodini web services with additional information to enable sustainability in cloud-native applications and the interface exporter to share the information.

## Hello World

This `HelloWorld` example shows the annotations of the Apodini web service with metadata and includes the `Sustainability` interface exporter in the configuration of the web service.

```
struct Greeter: Handler {
@Parameter var country: String?
func handle() -> String {
"Hello, \(country ?? "World")! 🚀"
}
var metadata: Metadata {
ExecutionModality(.highPerformance)
ResponseTime(value: 1)
InstanceType(.small)
}
}
struct GreeterService: Component {
var content: some Component {
Greeter()
.description("Say 'Hello' to a country.")
}
var metadata: Metadata {
Optional()
ServiceIdentifier("greeter")
}
}
struct HelloWorld: WebService {
@OptionGroup
var options: SustainabilityDocumentExportOptions
var configuration: Configuration {
Sustainability(options)
}
var content: some Component {
GreeterService()
}
}
```

## Contributing
Contributions to this project are welcome. Please make sure to read the [contribution guidelines](https://github.com/Apodini/.github/blob/main/CONTRIBUTING.md) and the [contributor covenant code of conduct](https://github.com/Apodini/.github/blob/main/CODE_OF_CONDUCT.md) first.

## License
This project is licensed under the MIT License. See [Licenses](https://github.com/Apodini/Template-Repository/tree/develop/LICENSES) for more information.
This project is licensed under the MIT License. See [Licenses](https://github.com/Apodini/ApodiniSustainability/tree/develop/LICENSES) for more information.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# ``ApodiniSustainability``
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import Apodini
import ApodiniDocumentExport
import ApodiniNetworking
@_implementationOnly import Logging

final class ApodiniSustainabilityInterfaceExporter: InterfaceExporter {

private let app: Application
private let configuration: SustainabilityDocumentConfiguration
private var services: [String: Service] = .init()
private let logger = Logger(label: "org.apodini.sustainability")

init(_ app: Application, configuration: SustainabilityDocumentConfiguration) {
self.app = app
self.configuration = configuration
}

func export<H>(_ endpoint: Endpoint<H>) -> () where H : Handler {

guard let key = endpoint[Context.self].get(valueFor: ServiceIdentifierComponentMetadata.self) else { return }

let requirements = Requirements(responseTime: endpoint[Context.self].get(valueFor: ResponseTimeHandlerMetadata.self), instanceType: endpoint[Context.self].get(valueFor: InstanceTypeHandlerMetadata.self))

let serviceVersion = ServiceVersion(id: endpoint[AnyHandlerIdentifier.self], name: endpoint[HandlerDescription.self].rawValue, description: endpoint[Context.self].get(valueFor: HandlerDescriptionMetadata.self), requirements: requirements)

let modalities: ExecutionModalities

switch endpoint[Context.self].get(valueFor: ExecutionModalityHandlerMetadata.self) {
case .highPerformance:
modalities = ExecutionModalities(highPerformance: serviceVersion)
case .lowPower:
modalities = ExecutionModalities(lowPower: serviceVersion)
default:
modalities = ExecutionModalities(standard: serviceVersion)
}

let optional : Bool? = endpoint[Context.self].get(valueFor: OptionalComponentMetadata.self)

services.merge([key : Service(
id: key,
name: key.replacingOccurrences(of: "-", with: " ").capitalized(with: nil),
description: String.init(),
optional: optional,
modalities: modalities
)]) { current, new in
var service = current
service.modalities = service.modalities.merge(with: new.modalities)
if new.optional ?? false {
service.optional = true
}
return service
}
}

func export<H>(blob endpoint: Endpoint<H>) -> () where H : Handler, H.Response.Content == Blob {
export(endpoint)
}

func finishedExporting(_ webService: WebServiceModel) {

let document = SustainabilityDocument(
serverPath: app.httpConfiguration.uriPrefix,
version: webService.context.get(valueFor: APIVersionContextKey.self).semVerString,
description: webService.context.get(valueFor: WebServiceDescriptionMetadata.self),
services: [Service](services.values)
)

app.storage.set(SustainabilityDocumentStorageKey.self, to: document)

guard let options = configuration.exportOptions else {
return logger.notice("No configuration provided to handle the ApodiniSustainability document of the current version")
}

if let directory = options.directory {
do {
let filePath = try document.write(at: directory, outputFormat: options.format, fileName: "sustainability_\(document.version)")
logger.info("ApodiniSustainability document exported at \(filePath) in \(options.format.rawValue)")
} catch {
logger.error("ApodiniSustainability export at \(directory) failed with error: \(error)")
}
}

if let endpoint = options.endpoint {
// app.httpServer.registerRoute(.GET, endpoint.httpPathComponents) { _ -> String in
// options.format.string(of: document)
// }
app.httpServer.registerRoute(.GET, endpoint.httpPathComponents) { req in
HTTPResponse(
version: req.version,
status: .ok,
headers: HTTPHeaders {
$0[.accessControlAllowOrigin] = .wildcard
},
bodyStorage: .buffer(initialValue: options.format.string(of: document))
)
}
logger.info("ApodiniSustainability document served at \(endpoint) in \(options.format.rawValue) format")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Apodini
import ApodiniDocumentExport

public enum ExecutionModalityOption: String, Value {
case standard
case highPerformance
case lowPower
}

public struct ExecutionModalityMetadataContextKey: ContextKey {
public static var defaultValue = ExecutionModalityOption.standard
public typealias Value = ExecutionModalityOption
}

extension HandlerMetadataNamespace {
/// Name definition for the ``ExecutionModalityHandlerMetadata``
public typealias ExecutionModality = ExecutionModalityHandlerMetadata
}

/// The ``ExecutionModalityHandlerMetadata`` can be used to describe the execution modality of a ``Handler``.
///
/// The Metadata is available under the ``HandlerMetadataNamespace/ExecutionModality`` name and can be used like the following:
/// ```swift
/// struct ExampleHandler: Handler {
/// // ...
/// var metadata: Metadata {
/// ExecutionModality(.highPerformance)
/// }
/// }
/// ```
public struct ExecutionModalityHandlerMetadata: HandlerMetadataDefinition {
public typealias Key = ExecutionModalityMetadataContextKey
public let value: ExecutionModalityOption

/// Creates a new `ExecutionModality` metadata
/// - Parameter value: The execution modality option for the Component.
public init(_ value: ExecutionModalityOption) {
self.value = value
}
}

extension Handler {
/// A `executionModality` modifier can be used to specify the `ExecutionModalityHandlerMetadata` via a `HandlerModifier`.
/// - Parameter value: The `executionModality` that is used for the `Handler`
/// - Returns: The modified `Handler` with the `ExecutionModalityHandlerMetadata` added.
public func executionModality(_ value: ExecutionModalityOption) -> HandlerMetadataModifier<Self> {
HandlerMetadataModifier(modifies: self, with: ExecutionModalityHandlerMetadata(value))
}
}
48 changes: 48 additions & 0 deletions Sources/ApodiniSustainability/Metadata/InstanceTypeMetadata.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Apodini
import ApodiniDocumentExport

public enum InstanceTypeOption: String, Value {
case large
case medium
case small
}

public struct InstanceTypeMetadataContextKey: OptionalContextKey {
public typealias Value = InstanceTypeOption
}

extension HandlerMetadataNamespace {
/// Name definition for the ``InstanceTypeHandlerMetadata``
public typealias InstanceType = InstanceTypeHandlerMetadata
}

/// The ``InstanceTypeHandlerMetadata`` can be used to describe the instance type of a ``Handler``.
///
/// The Metadata is available under the ``HandlerMetadataNamespace/InstanceType`` name and can be used like the following:
/// ```swift
/// struct ExampleHandler: Handler {
/// // ...
/// var metadata: Metadata {
/// InstanceType(.small)
/// }
/// }
/// ```
public struct InstanceTypeHandlerMetadata: HandlerMetadataDefinition {
public typealias Key = InstanceTypeMetadataContextKey
public let value: InstanceTypeOption

/// Creates a new `InstanceType` metadata
/// - Parameter value: The instance type option for the Component.
public init(_ value: InstanceTypeOption) {
self.value = value
}
}

extension Handler {
/// A `instanceType` modifier can be used to specify the `InstanceTypeHandlerMetadata` via a `HandlerModifier`.
/// - Parameter value: The `instanceType` that is used for the `Handler`
/// - Returns: The modified `Handler` with the `InstanceTypeHandlerMetadata` added.
public func instanceType(_ value: InstanceTypeOption) -> HandlerMetadataModifier<Self> {
HandlerMetadataModifier(modifies: self, with: InstanceTypeHandlerMetadata(value))
}
}
Loading

0 comments on commit e55652f

Please sign in to comment.