diff --git a/Makefile b/Makefile index dd8ad66..762fd2f 100644 --- a/Makefile +++ b/Makefile @@ -17,3 +17,11 @@ download-oas: generate-sdk: @$(SCRIPTS_BASE)/generate-sdk/generate-sdk.sh "$(GIT_HOST)" "$(GIT_USER_ID)" "$(GIT_REPO_ID)" "$(SDK_REPO_URL)" "$(LANGUAGE)" "$(SDK_BRANCH)" +generate-go-sdk: + @$(SCRIPTS_BASE)/generate-sdk/generate-sdk.sh "$(GIT_HOST)" "$(GIT_USER_ID)" "$(GIT_REPO_ID)" "$(SDK_REPO_URL)" "go" "$(SDK_BRANCH)" + +generate-python-sdk: + @$(SCRIPTS_BASE)/generate-sdk/generate-sdk.sh "$(GIT_HOST)" "$(GIT_USER_ID)" "$(GIT_REPO_ID)" "$(SDK_REPO_URL)" "python" "$(SDK_BRANCH)" + +generate-java-sdk: + @$(SCRIPTS_BASE)/generate-sdk/generate-sdk.sh "$(GIT_HOST)" "$(GIT_USER_ID)" "$(GIT_REPO_ID)" "$(SDK_REPO_URL)" "java" "$(SDK_BRANCH)" \ No newline at end of file diff --git a/README.md b/README.md index 72666fd..3a8a083 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,29 @@ If you want to modify script or templates and you can run code locally. Requires `Go 1.21` or higher. 1. Set up the project and tools by running - ``` + ```bash make project-tools ``` 2. Download Open Api Specifications (OAS), that are the input for the SDK generation, by running - ``` + ```bash make download-oas ``` This step needs to be done only at the first start and when OAS updates are present. -3. Run the SDK generation for testing by +3. Run the Go SDK generation for testing by + ```bash + make generate-go-sdk + ``` + The output goes to the `./sdk-repo-updated` folder. + 1. Run the Python SDK generation for testing by + ```bash + make generate-python-sdk ``` - make generate-sdk + The output goes to the `./sdk-repo-updated` folder. + 2. Run the Java SDK generation for testing by + ```bash + make generate-java-sdk ``` - The output goes to the `./sdk` folder. + The output goes to the `./sdk-repo-updated` folder. ## Reporting issues diff --git a/openapi-generator-config-java.yml b/openapi-generator-config-java.yml new file mode 100644 index 0000000..19e3ae0 --- /dev/null +++ b/openapi-generator-config-java.yml @@ -0,0 +1,14 @@ +templateDir: templates/java +additionalProperties: + artifactUrl: https://github.com/stackitcloud/stackit-sdk-java + developerName: STACKIT Developer Tools + developerEmail: developer-tools@stackit.cloud + developerOrganization: STACKIT + developerOrganizationUrl: https://www.stackit.de + groupId: cloud.stackit + hideGenerationTimestamp: true + licenseName: Apache License 2.0 + licenseUrl: http://www.apache.org/licenses/ + scmConnection: scm:git@github.com:stackitcloud/stackit-sdk-java.git + scmDeveloperConnection: scm:git@github.com:stackitcloud/stackit-sdk-java.git + scmUrl: https://github.com/stackitcloud/stackit-sdk-java \ No newline at end of file diff --git a/scripts/generate-sdk/.openapi-generator-ignore-java b/scripts/generate-sdk/.openapi-generator-ignore-java new file mode 100644 index 0000000..653a423 --- /dev/null +++ b/scripts/generate-sdk/.openapi-generator-ignore-java @@ -0,0 +1,9 @@ +git_push.sh +.travis.yml +.gitignore +api/openapi.yaml +tox.ini +**/.github/** +**/AndroidManifest.xml +pom.xml +build.sbt \ No newline at end of file diff --git a/scripts/generate-sdk/generate-sdk.sh b/scripts/generate-sdk/generate-sdk.sh index aec217b..7d45bef 100755 --- a/scripts/generate-sdk/generate-sdk.sh +++ b/scripts/generate-sdk/generate-sdk.sh @@ -57,6 +57,10 @@ go) GENERATOR_VERSION="v6.6.0" # There are issues with GO SDK generation in version v7 ;; python) +# Renovate: datasource=github-tags depName=OpenAPITools/openapi-generator versioning=semver + GENERATOR_VERSION="v7.14.0" + ;; +java) # Renovate: datasource=github-tags depName=OpenAPITools/openapi-generator versioning=semver GENERATOR_VERSION="v7.14.0" ;; @@ -94,6 +98,13 @@ python) # Usage: generate_python_sdk GENERATOR_PATH GIT_HOST GIT_USER_ID [GIT_REPO_ID] [SDK_REPO_URL] [SDK_BRANCH] generate_python_sdk "${jar_path}" "${GIT_HOST}" "${GIT_USER_ID}" "${GIT_REPO_ID}" "${SDK_REPO_URL}" "${SDK_BRANCH}" ;; +java) + echo -e "\n>> Generating the Java SDK..." + + source ${LANGUAGE_GENERATORS_FOLDER_PATH}/${LANGUAGE}.sh + # Usage: generate_java_sdk GENERATOR_PATH GIT_HOST GIT_USER_ID [GIT_REPO_ID] [SDK_REPO_URL] [SDK_BRANCH] + generate_java_sdk "${jar_path}" "${GIT_HOST}" "${GIT_USER_ID}" "${GIT_REPO_ID}" "${SDK_REPO_URL}" "${SDK_BRANCH}" + ;; *) echo "! SDK language not supported." exit 1 diff --git a/scripts/generate-sdk/languages/java.sh b/scripts/generate-sdk/languages/java.sh new file mode 100644 index 0000000..8121cae --- /dev/null +++ b/scripts/generate-sdk/languages/java.sh @@ -0,0 +1,160 @@ +#!/bin/bash +# This script clones the SDK repo and updates it with the generated API modules +# Pre-requisites: Java +set -eo pipefail + +ROOT_DIR=$(git rev-parse --show-toplevel) +SDK_REPO_LOCAL_PATH="${ROOT_DIR}/sdk-repo-updated" + +SERVICES_FOLDER="${SDK_REPO_LOCAL_PATH}/services" + +GENERATOR_LOG_LEVEL="error" # Must be a Java log level (error, warn, info...) + +INCLUDE_SERVICES=("resourcemanager") + +generate_java_sdk() { + # Required parameters + local GENERATOR_JAR_PATH=$1 + local GIT_HOST=$2 + local GIT_USER_ID=$3 + + # Optional parameters + local GIT_REPO_ID=$4 + local SDK_REPO_URL=$5 + local SDK_BRANCH=$6 + + # Check required parameters + if [[ -z ${GIT_HOST} ]]; then + echo "! GIT_HOST not specified." + exit 1 + fi + + if [[ -z ${GIT_USER_ID} ]]; then + echo "! GIT_USER_ID id not specified." + exit 1 + fi + + # Check optional parameters and set defaults if not provided + if [[ -z ${GIT_REPO_ID} ]]; then + echo "GIT_REPO_ID not specified, default will be used." + GIT_REPO_ID="stackit-sdk-java" + fi + + if [[ -z ${SDK_REPO_URL} ]]; then + echo "SDK_REPO_URL not specified, default will be used." + SDK_REPO_URL="https://github.com/stackitcloud/stackit-sdk-java.git" + fi + + # Prepare folders + if [[ ! -d $SERVICES_FOLDER ]]; then + mkdir -p "$SERVICES_FOLDER" + fi + + # Clone SDK repo + if [ -d ${SDK_REPO_LOCAL_PATH} ]; then + echo "Old SDK repo clone was found, it will be removed." + rm -rf ${SDK_REPO_LOCAL_PATH} + fi + git clone --quiet -b ${SDK_BRANCH} ${SDK_REPO_URL} ${SDK_REPO_LOCAL_PATH} + + # Backup of the current state of the SDK services dir (services/) + sdk_services_backup_dir=$(mktemp -d) + if [[ ! ${sdk_services_backup_dir} || -d {sdk_services_backup_dir} ]]; then + echo "! Unable to create temporary directory" + exit 1 + fi + cleanup() { + rm -rf ${sdk_services_backup_dir} + } + cp -a "${SERVICES_FOLDER}/." ${sdk_services_backup_dir} + + # Cleanup after we are done + trap cleanup EXIT + + # Remove old contents of services dir (services/) + rm -rf ${SERVICES_FOLDER} + + # Generate SDK for each service + for service_json in ${ROOT_DIR}/oas/*.json; do + service="${service_json##*/}" + service="${service%.json}" + + # Remove invalid characters to ensure a valid Java pkg name + service="${service//-/}" # remove dashes + service="${service// /}" # remove spaces + service=$(echo "${service}" | tr '[:upper:]' '[:lower:]') # convert upper case letters to lower case + service=$(echo "${service}" | tr -d -c '[:alnum:]') # remove non-alphanumeric characters + + # Ensure the package name doesn't start with a number + if [[ "${service}" =~ ^[0-9] ]]; then + service="_${service}" # Prepend a valid prefix if it starts with a number + fi + + if ! [[ ${INCLUDE_SERVICES[*]} =~ ${service} ]]; then + echo "Skipping not included service ${service}" + continue + fi + + if grep -E "^$service$" ${ROOT_DIR}/blacklist.txt; then + echo "Skipping blacklisted service ${service}" + continue + fi + + echo ">> Generating \"${service}\" service..." + cd ${ROOT_DIR} + + mkdir -p "${SERVICES_FOLDER}/${service}/" + cp "${ROOT_DIR}/scripts/generate-sdk/.openapi-generator-ignore-java" "${SERVICES_FOLDER}/${service}/.openapi-generator-ignore" + + SERVICE_DESCRIPTION=$(cat ${service_json} | jq .info.title --raw-output) + + # Run the generator + java -Dlog.level=${GENERATOR_LOG_LEVEL} -jar ${jar_path} generate \ + --generator-name java \ + --input-spec "${service_json}" \ + --output "${SERVICES_FOLDER}/${service}" \ + --git-host ${GIT_HOST} \ + --git-user-id ${GIT_USER_ID} \ + --git-repo-id ${GIT_REPO_ID} \ + --global-property apis,models,modelTests=false,modelDocs=false,apiDocs=false,apiTests=false,supportingFiles \ + --additional-properties=artifactId="stackit-sdk-${service}",artifactDescription="${SERVICE_DESCRIPTION}",invokerPackage="cloud.stackit.sdk.${service}",modelPackage="cloud.stackit.sdk.${service}.model",apiPackage="cloud.stackit.sdk.${service}.api" >/dev/null \ + --http-user-agent stackit-sdk-java/"${service}" \ + --config openapi-generator-config-java.yml + + # Remove unnecessary files + rm "${SERVICES_FOLDER}/${service}/.openapi-generator-ignore" + rm -r "${SERVICES_FOLDER}/${service}/.openapi-generator/" + rm "${SERVICES_FOLDER}/${service}/.github/workflows/maven.yml" + + # If the service has a README.md file, move them inside the service folder + if [ -f ${sdk_services_backup_dir}/${service}/README.md ]; then + echo "Found ${service} \"README.md\" file" + cp -r ${sdk_services_backup_dir}/${service}/README.md ${SERVICES_FOLDER}/${service}/README.md + fi + + # If the service has a CHANGELOG file, move it inside the service folder + if [ -f ${sdk_services_backup_dir}/${service}/CHANGELOG.md ]; then + echo "Found ${service} \"CHANGELOG\" file" + cp -r ${sdk_services_backup_dir}/${service}/CHANGELOG.md ${SERVICES_FOLDER}/${service}/CHANGELOG.md + fi + + # If the service has a LICENSE file, move it inside the service folder + if [ -f ${sdk_services_backup_dir}/${service}/LICENSE.md ]; then + echo "Found ${service} \"LICENSE\" file" + cp -r ${sdk_services_backup_dir}/${service}/LICENSE.md ${SERVICES_FOLDER}/${service}/LICENSE.md + fi + + # If the service has a NOTICE file, move it inside the service folder + if [ -f ${sdk_services_backup_dir}/${service}/NOTICE.txt ]; then + echo "Found ${service} \"NOTICE\" file" + cp -r ${sdk_services_backup_dir}/${service}/NOTICE.txt ${SERVICES_FOLDER}/${service}/NOTICE.txt + fi + + # If the service has a VERSION file, move it inside the service folder + if [ -f ${sdk_services_backup_dir}/${service}/VERSION ]; then + echo "Found ${service} \"VERSION\" file" + cp -r ${sdk_services_backup_dir}/${service}/VERSION ${SERVICES_FOLDER}/${service}/VERSION + fi + + done +} diff --git a/templates/java/README.md b/templates/java/README.md new file mode 100644 index 0000000..75f1b25 --- /dev/null +++ b/templates/java/README.md @@ -0,0 +1,14 @@ +# Java templates + +This folder contains only our customized Java templates. Beside these customized templates, +the original templates of openapi-generator for Java are used. These can be found in the +official GitHub repo of the [openapi-generator](https://github.com/OpenAPITools/openapi-generator/tree/v7.14.0/modules/openapi-generator/src/main/resources/Java). + +If you need to change something in the Java Generator, try always first to add +[user-defined templates](https://openapi-generator.tech/docs/customization#user-defined-templates), +instead of overwriting existing templates. These ensure an easier upgrade process, to newer +versions of the openapi-generator. + +If it's required to customize the original templates, you can copy them into this directory. +Try to minimize the customization as much as possible, to ensure, that we can easily upgrade +to newer versions in the future. diff --git a/templates/java/README.mustache b/templates/java/README.mustache new file mode 100644 index 0000000..f3e06d8 --- /dev/null +++ b/templates/java/README.mustache @@ -0,0 +1,179 @@ +# {{artifactId}} + +{{appName}} + +- API version: {{appVersion}} + +{{{appDescriptionWithNewLines}}} + +{{#infoUrl}} +For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +This package is part of the STACKIT Java SDK. For additional information, please visit the [GitHub repository](https://{{gitHost}}/{{{gitUserId}}}/{{{gitRepoId}}}) of the SDK. + + +## Requirements + +Building the API client library requires: +1. Java 1.8+ + +## Installation + +To install the API client library to your local Maven repository, simply execute: + +```shell +./gradlew publishToMavenLocal +``` + +To deploy it to a remote Maven repository instead, configure the settings of the repository and execute: + +```shell +# TODO: follow up story +# ./gradlew publishToMavenCentral +``` + +Refer to the [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) for more information. + +### Maven users + +Add this dependency to your project's POM: + +```xml + + {{{groupId}}} + {{{artifactId}}} + + compile + +``` + +### Gradle users + +Add this dependency to your project's build file: + +```groovy + repositories { + mavenCentral() // Needed if the '{{{artifactId}}}' jar has been published to maven central. + mavenLocal() // Needed if the '{{{artifactId}}}' jar has been published to the local maven repo. + } + + dependencies { + implementation "{{{groupId}}}:{{{artifactId}}}:" + } +``` + +### Others + +At first generate the JAR by executing: + +```shell +mvn clean package +``` + +Then manually install the following JARs: + +- `target/{{{artifactId}}}-.jar` +- `target/lib/*.jar` + +{{#jersey2}} +## Usage + +To add a HTTP proxy for the API client, use `ClientConfig`: +```java +{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}} +import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import {{{invokerPackage}}}.*; +import {{{package}}}.{{{classname}}}; + +... + +ApiClient defaultClient = Configuration.getDefaultApiClient(); +ClientConfig clientConfig = defaultClient.getClientConfig(); +clientConfig.connectorProvider(new ApacheConnectorProvider()); +clientConfig.property(ClientProperties.PROXY_URI, "http://proxy_url_here"); +clientConfig.property(ClientProperties.PROXY_USERNAME, "proxy_username"); +clientConfig.property(ClientProperties.PROXY_PASSWORD, "proxy_password"); +defaultClient.setClientConfig(clientConfig); + +{{{classname}}} apiInstance = new {{{classname}}}(defaultClient); +{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}} +``` + +{{/jersey2}} +## Getting Started + +Please follow the [installation](#installation) instruction and execute the following Java code: + +```java +{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}} +import {{{invokerPackage}}}.*; +import {{{invokerPackage}}}.auth.*; +import {{{modelPackage}}}.*; +import {{{package}}}.{{{classname}}}; + +public class {{{classname}}}Example { + + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("{{{basePath}}}"); + {{#hasAuthMethods}}{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} + // Configure HTTP basic authorization: {{{name}}} + HttpBasicAuth {{{name}}} = (HttpBasicAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setUsername("YOUR USERNAME"); + {{{name}}}.setPassword("YOUR PASSWORD");{{/isBasicBasic}}{{#isBasicBearer}} + // Configure HTTP bearer authorization: {{{name}}} + HttpBearerAuth {{{name}}} = (HttpBearerAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setBearerToken("BEARER TOKEN");{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} + // Configure API key authorization: {{{name}}} + ApiKeyAuth {{{name}}} = (ApiKeyAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //{{{name}}}.setApiKeyPrefix("Token");{{/isApiKey}}{{#isOAuth}} + // Configure OAuth2 access token for authorization: {{{name}}} + OAuth {{{name}}} = (OAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setAccessToken("YOUR ACCESS TOKEN");{{/isOAuth}}{{#isHttpSignature}} + // Configure HTTP signature authorization: {{{name}}} + HttpSignatureAuth {{{name}}} = (HttpSignatureAuth) defaultClient.getAuthentication("{{{name}}}"); + // All the HTTP signature parameters below should be customized to your environment. + // Configure the keyId + {{{name}}}.setKeyId("YOUR KEY ID"); + // Configure the signature algorithm + {{{name}}}.setSigningAlgorithm(SigningAlgorithm.HS2019); + // Configure the specific cryptographic algorithm + {{{name}}}.setAlgorithm(Algorithm.ECDSA_SHA256); + // Configure the cryptographic algorithm parameters, if applicable + {{{name}}}.setAlgorithmParameterSpec(null); + // Set the cryptographic digest algorithm. + {{{name}}}.setDigestAlgorithm("SHA-256"); + // Set the HTTP headers that should be included in the HTTP signature. + {{{name}}}.setHeaders(Arrays.asList("date", "host")); + // Set the private key used to sign the HTTP messages + {{{name}}}.setPrivateKey();{{/isHttpSignature}} + {{/authMethods}} + {{/hasAuthMethods}} + + {{{classname}}} apiInstance = new {{{classname}}}(defaultClient); + {{#allParams}} + {{{dataType}}} {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}} + {{/allParams}} + try { + {{#returnType}}{{{.}}} result = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}} + System.out.println(result);{{/returnType}} + } catch (ApiException e) { + System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}} +``` + +## Recommendation + +It's recommended to create an instance of `ApiClient` per thread in a multithreaded environment to avoid any potential issues.