Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5e2ecb3
chore: bump to 0.0.8-SNAPSHOT
Jonathan-Zollinger Jan 29, 2026
fd2fd6c
feat: allow project to org assignment
Jonathan-Zollinger Jan 29, 2026
a9263de
fix: don't print logs of any level to console
Jonathan-Zollinger Jan 29, 2026
26bebf9
Feat make create user helper private (#64)
HMS-Victory Feb 4, 2026
fc3855d
Tests querying orgs by slug (#66)
HMS-Victory Feb 5, 2026
30741bb
build: remove test tasks
Jonathan-Zollinger Feb 27, 2026
7e80758
refactor!: add core submodule
Jonathan-Zollinger Feb 27, 2026
043fe47
fix: create user with multipart request
Jonathan-Zollinger Mar 2, 2026
2c48eed
fix: add retry for intermittent failures
Jonathan-Zollinger Mar 3, 2026
f63ccf5
refactor: add cli submodule
Jonathan-Zollinger Mar 4, 2026
c1b2512
refactor(test): reuse user and orgs between tests
Jonathan-Zollinger Mar 4, 2026
fa6e641
fix(build): define main class in cli
Jonathan-Zollinger Mar 4, 2026
b7a00ad
ci: set workflow to test core and cli modules
Jonathan-Zollinger Mar 4, 2026
cd6c132
feat: add getAllUserInformation()
Jonathan-Zollinger Mar 4, 2026
20d839a
fix: accommodate year-zero date-time response
Jonathan-Zollinger Mar 4, 2026
3aa0adc
refactor: rename root project to devkit
Jonathan-Zollinger Mar 6, 2026
41f5a68
add graph api calls (#73)
Jonathan-Zollinger Mar 9, 2026
70383c5
refactor: simplify annotations
Jonathan-Zollinger Mar 9, 2026
1af15e1
refactor(test): make test auth client more clear
Jonathan-Zollinger Mar 9, 2026
6cff0a3
refactor: set strong types for dates and emails
Jonathan-Zollinger Mar 9, 2026
851bfbd
refactor CreateEvent method with dynamic query builder (#88)
Jonathan-Zollinger Mar 17, 2026
f0a3a5a
test: test searchOrganization() query parser
HMS-Victory Mar 19, 2026
f888190
fix(test): expect 201 or 200 when creating users
Jonathan-Zollinger Mar 23, 2026
b2dd7bf
fix(test): set auth as "" in unauthenticated context
Jonathan-Zollinger Mar 23, 2026
f18239d
introduce mock emails in tests
Jonathan-Zollinger Mar 24, 2026
b50b330
refactor: throw error if graph response has errors
Jonathan-Zollinger Apr 24, 2026
b2710dc
build: set versions in gradle.properties
Jonathan-Zollinger Apr 24, 2026
e2cc6f8
refactor: use lombok to set logger
Jonathan-Zollinger Apr 24, 2026
7b57384
refactor(test): create new projects between tests
Jonathan-Zollinger Apr 24, 2026
7c006ae
refactor(test): Tests only need JUSTSERVE_TOKEN
Jonathan-Zollinger Apr 25, 2026
4c594d7
docs: revamp README
Jonathan-Zollinger Apr 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 8 additions & 15 deletions .github/workflows/TestAndCompile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,20 @@ name: GraalVM Native Image Test run and build tests
on: [pull_request, workflow_dispatch]
jobs:
build:
name: test and compile on ${{ matrix.os }}
name: test and compile on self-hosted
runs-on: self-hosted
# runs-on: ${{ matrix.os }}
# strategy:
# matrix:
# os: [macos-latest, windows-latest, ubuntu-latest]
env:
TEST_TOKEN: ${{ secrets.MICRONAUT_HTTP_SERVICES_JUSTSERVE_TOKEN }}
JAVA_HOME: ${{ secrets.JAVA_HOME }}

steps:
- uses: actions/checkout@v4
- uses: graalvm/setup-graalvm@v1
with:
java-version: '21'
distribution: 'graalvm'
github-token: ${{ secrets.GITHUB_TOKEN }}
native-image-job-reports: 'true'
- name: call gradle to run tests and then compile
run: ./gradlew test nativeCompile
- name: Run core tests
run: ./gradlew :core:test
- name: Run cli tests and compile
run: ./gradlew :cli:test :cli:nativeCompile
- name: upload binary
uses: actions/upload-artifact@v4
with:
# name: justserve-${{ matrix.os }}
name: justserve
path: build/native/nativeCompile/justserve*
path: cli/build/native/nativeCompile/justserve*
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
}
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
## JustServe Cli Tool
# JustServe Resources

The JustServe Cli tool is an admin tool for JustServe Specialists and administrators.
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=JustServe-Resources_cli&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=JustServe-Resources_cli)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=JustServe-Resources_cli&metric=coverage)](https://sonarcloud.io/summary/new_code?id=JustServe-Resources_cli)
[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=JustServe-Resources_cli&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=JustServe-Resources_cli)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=JustServe-Resources_cli&metric=bugs)](https://sonarcloud.io/summary/new_code?id=JustServe-Resources_cli)
[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=JustServe-Resources_cli&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=JustServe-Resources_cli)
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=JustServe-Resources_cli&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=JustServe-Resources_cli)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=JustServe-Resources_cli&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=JustServe-Resources_cli)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=JustServe-Resources_cli&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=JustServe-Resources_cli)

This tool is very much under development and whose api is subject to change with each release. Standard versioning is used for this project to delineate breaking releases.
This repository is architected as a modular, multi-module Micronaut project. At its center is the `core` module, a
reusable library providing a thoroughly tested HTTP client for the JustServe API.

Other modules leverage the `core` library to build specific applications. The `cli` module, for instance, is a GraalVM native command-line application that consumes the `core` client to provide administrative tooling.

As the project evolves, we adhere to semantic versioning. The API is subject to change, and any breaking modifications will be clearly communicated through major version increments.

## Cli Tool

### Install

Expand Down Expand Up @@ -37,7 +51,7 @@ echo $env:java_home
</ol>
</details>

To generate the executable for your system, run `./gradlew nativeCompile`. The executable will be generated in the build directory (`\build\native\nativeCompile\`).
To generate the executable for your system, run `./gradlew :cli:nativeCompile`. The executable will be generated in the build directory (`cli/build/native/nativeCompile/`).

### Authenticate

Expand Down
90 changes: 1 addition & 89 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,93 +1,5 @@
import org.apache.tools.ant.filters.ReplaceTokens
import java.util.*

plugins {
id("groovy")
id("io.micronaut.application") version "4.5.3"
id("com.gradleup.shadow") version "8.3.6"
id("io.micronaut.openapi") version "4.5.3"
id("org.graalvm.buildtools.native") version "0.10.6"
id("org.asciidoctor.jvm.convert") version "3.3.2"
}

version = project.properties["justserveCliVersion"]!!
group = "org.justserve"

apply(from = "gradle/asciidoc.gradle")

repositories {
mavenCentral()
}

dependencies {
annotationProcessor("org.projectlombok:lombok")
annotationProcessor("info.picocli:picocli-codegen")
annotationProcessor("io.micronaut.serde:micronaut-serde-processor")
implementation("info.picocli:picocli")
implementation("info.picocli:picocli-jansi-graalvm:1.2.0")
implementation("org.fusesource.jansi:jansi:2.4.2")
implementation("info.picocli:picocli-shell-jline3:4.7.6")
implementation("org.jline:jline:3.30.5")
implementation("io.micronaut:micronaut-http-client")
implementation("io.micronaut.picocli:micronaut-picocli")
implementation("io.micronaut.serde:micronaut-serde-jackson")
implementation("io.micronaut:micronaut-retry")
implementation("org.simplejavamail:simple-java-mail:8.12.6")
implementation("org.jsoup:jsoup:1.21.2")
testImplementation("net.datafaker:datafaker:2.5.1")
compileOnly("org.projectlombok:lombok")
runtimeOnly("ch.qos.logback:logback-classic")
runtimeOnly("org.yaml:snakeyaml")
id("it.nicolasfarabegoli.conventional-commits") version "3.1.3"
}


application {
mainClass = "org.justserve.cli.JustServeCommand"
}

java {
sourceCompatibility = JavaVersion.toVersion("21")
targetCompatibility = JavaVersion.toVersion("21")
}

tasks.withType<ProcessResources> {
val props = Properties()
file("gradle.properties").inputStream().use { props.load(it) }
filesMatching("**/application.yml") {
filter(mapOf("tokens" to props), ReplaceTokens::class.java)
}
}

micronaut {
testRuntime("spock2")
openapi {
version = "6.16.0"
client(file("src/main/resources/schema.yml")) {
apiPackageName = "org.justserve.client"
modelPackageName = "org.justserve.model"
useReactive = false
useAuth = false
lombok.set(true)
clientId = "justserve"
apiNameSuffix = "Client"
alwaysUseGenerateHttpResponse = true
}
}
processing {
incremental(true)
annotations("org.justserve.*")
}
}

graalvmNative.binaries {
named("main") {
imageName.set("justserve")
buildArgs.add("--color=always")
buildArgs.add("-march=native")
}
}


tasks.named<io.micronaut.gradle.docker.NativeImageDockerfile>("dockerfileNative") {
jdkVersion = "21"
}
78 changes: 78 additions & 0 deletions cli/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import org.apache.tools.ant.filters.ReplaceTokens
import java.util.*

plugins {
id("groovy")
id("io.micronaut.application") version "4.6.2"
id("com.gradleup.shadow") version "8.3.9"
id("org.graalvm.buildtools.native") version "0.10.6"
}

group = "org.justserve"
version = project.properties["justserveCliVersion"]!!

repositories {
mavenCentral()
}

dependencies {
annotationProcessor("org.projectlombok:lombok")
annotationProcessor("info.picocli:picocli-codegen")
annotationProcessor("io.micronaut.serde:micronaut-serde-processor")
annotationProcessor("io.micronaut.validation:micronaut-validation")
implementation("io.micronaut.validation:micronaut-validation")
implementation("info.picocli:picocli")
implementation("info.picocli:picocli-jansi-graalvm:1.2.0")
implementation("org.fusesource.jansi:jansi:2.4.2")
implementation("info.picocli:picocli-shell-jline3:4.7.6")
implementation("org.jline:jline:3.30.5")
implementation("io.micronaut.picocli:micronaut-picocli")
implementation("org.simplejavamail:simple-java-mail:8.12.6")
implementation("io.micronaut.serde:micronaut-serde-jackson")
implementation("io.micronaut:micronaut-http-client")
implementation("org.simplejavamail:simple-java-mail:8.12.6")
implementation("org.jsoup:jsoup:1.21.2")
implementation(project(":core"))
testImplementation("net.datafaker:datafaker:2.5.1")
testImplementation("org.apache.commons:commons-lang3:3.20.0")
testImplementation(project(path = ":core", configuration = "testArchives"))
compileOnly("org.projectlombok:lombok")
runtimeOnly("ch.qos.logback:logback-classic")
runtimeOnly("org.yaml:snakeyaml")
}


application {
mainClass = "org.justserve.JustServeCommand"
}
java {
sourceCompatibility = JavaVersion.toVersion("21")
targetCompatibility = JavaVersion.toVersion("21")
}

tasks.withType<ProcessResources> {
val props = Properties()
file("../gradle.properties").inputStream().use { props.load(it) }
filesMatching("**/application.yml") {
filter(mapOf("tokens" to props), ReplaceTokens::class.java)
}
}

micronaut {
testRuntime("spock2")
processing {
incremental(true)
annotations("org.justserve.*")
}
}

tasks.named<io.micronaut.gradle.docker.NativeImageDockerfile>("dockerfileNative") {
jdkVersion = "21"
}

graalvmNative.binaries {
named("main") {
imageName.set("justserve")
buildArgs.add("--color=always")
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package org.justserve.cli;
package org.justserve;

import io.micronaut.configuration.picocli.PicocliRunner;
import org.justserve.cli.command.BaseCommand;
import org.justserve.cli.command.GetTempPassword;
import org.justserve.cli.command.MakeOrgAdmin;
import org.justserve.cli.command.UnReassignProjects;
import org.justserve.cli.util.JustServeVersionProvider;
import org.justserve.command.*;
import org.justserve.util.JustServeVersionProvider;
import picocli.CommandLine.Command;
import picocli.CommandLine.ParameterException;
import picocli.jansi.graalvm.AnsiConsole;

@Command(subcommands = {GetTempPassword.class, MakeOrgAdmin.class, UnReassignProjects.class},
@Command(subcommands = {GetTempPassword.class, MakeOrgAdmin.class, UnReassignProjects.class, AssignOrgToProject.class},
mixinStandardHelpOptions = true,
name = "justserve", versionProvider = JustServeVersionProvider.class,
description = "justserve-cli is a terminal tool to help specialists and admin using JustServe")
Expand Down
58 changes: 58 additions & 0 deletions cli/src/main/java/org/justserve/command/AssignOrgToProject.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.justserve.command;

import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import lombok.extern.slf4j.Slf4j;
import org.justserve.client.ProjectClient;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;

import java.util.UUID;

import static org.justserve.util.JustServePrinter.printError;
import static org.justserve.util.JustServePrinter.printNormal;


@Slf4j
@Command(name = "assignOrgToProject", description = "Assigns an organization to a project", mixinStandardHelpOptions = true)
public class AssignOrgToProject extends BaseCommand implements Runnable {

@Option(names = {"--project", "-p"}, description = "the project ID", required = true)
private UUID projectId;

@Option(names = {"--org", "-o"}, description = "the organization ID", required = true)
private UUID orgId;

@Inject
Provider<ProjectClient> projectClientProvider;

@Override
public void run() {
if (isTokenInvalid()) {
return;
}

ProjectClient client = projectClientProvider.get();

try {
log.atTrace().log("Assigning organization {} to project {}", orgId, projectId);
HttpResponse<Object> response = client.assignOrganizationToProject(projectId, orgId);
if (response.status() == HttpStatus.OK) {
printNormal("Successfully assigned organization %s to project %s", orgId, projectId);
log.atTrace().log("received api response status: {}", response.status());
} else {
printError("Failed to assign organization " + orgId + " to project " + projectId +
". Expected HTTP Status 'OK', but got " + response.status());
log.atError().log("Failed to assign organization {} to project {}. Expected HTTP Status 'OK', but got {}",
orgId, projectId, response.status());
}
} catch (HttpClientResponseException e) {
printError("Failed to assign organization %s to project %s. (%s: %s)",
orgId, projectId, e.getStatus().getCode(), e.getMessage());
log.atError().setCause(e).log("Error response from API: {}", e.getResponse().body());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.justserve.cli.command;
package org.justserve.command;

import io.micronaut.context.annotation.Value;
import io.micronaut.core.annotation.NonNull;
Expand All @@ -10,7 +10,7 @@
import java.io.PrintWriter;
import java.util.Optional;

import static org.justserve.cli.util.JustServePrinter.printError;
import static org.justserve.util.JustServePrinter.printError;
import static picocli.CommandLine.Help.Ansi.AUTO;

@Command
Expand All @@ -24,7 +24,7 @@ public class BaseCommand implements ConsoleOutput {
String token;

boolean isTokenInvalid() {
if ("i-need-to-be-defined".equals(token) || null == token) {
if ("i-need-to-be-defined".equals(token) || null == token || token.isEmpty()) {
printError(("NO AUTHENTICATION PROVIDED" + System.lineSeparator() +
"The Authentication token is not assigned as an environment variable." + System.lineSeparator() +
"Please define the environment variable \"JUSTSERVE_TOKEN\" and try again."));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.justserve.cli.command;
package org.justserve.command;

import io.micronaut.core.annotation.ReflectiveAccess;
import org.justserve.cli.util.JustServeVersionProvider;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;

Expand All @@ -20,7 +19,7 @@
* @author Jonathan Zollinger
* @version 0.0.1
*/
@Command(mixinStandardHelpOptions = true, versionProvider = JustServeVersionProvider.class)
@Command(mixinStandardHelpOptions = true, versionProvider = org.justserve.util.JustServeVersionProvider.class)
@SuppressWarnings("checkstyle:VisibilityModifier")
public class CommonOptionsMixin {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.justserve.cli.command;
package org.justserve.command;

public interface ConsoleOutput {
ConsoleOutput NOOP = new ConsoleOutput() {
Expand Down
Loading
Loading