From e7f530d98078b0e468996eafd5746dbf0a6f18e7 Mon Sep 17 00:00:00 2001 From: "Gerard Castillo Lasheras (BI X)" Date: Tue, 19 Dec 2023 21:59:17 +0100 Subject: [PATCH] first Rust Axum quickstarter commit --- be-rust-axum/Jenkinsfile | 38 ++++ be-rust-axum/Jenkinsfile.template | 72 ++++++++ be-rust-axum/README.md | 7 + be-rust-axum/ocp.env | 4 + .../rust-template/.config/nextest.toml | 4 + be-rust-axum/rust-template/.editorconfig | 14 ++ be-rust-axum/rust-template/.gitignore | 5 + .../rust-template/.pre-commit-config.yaml | 5 + be-rust-axum/rust-template/Cargo.toml | 42 +++++ be-rust-axum/rust-template/chart/.helmignore | 23 +++ be-rust-axum/rust-template/chart/Chart.yaml | 24 +++ .../rust-template/chart/templates/NOTES.txt | 1 + .../chart/templates/_helpers.tpl | 62 +++++++ .../chart/templates/deployment.yaml | 78 +++++++++ .../chart/templates/service.yaml | 15 ++ .../templates/tests/test-connection.yaml | 15 ++ be-rust-axum/rust-template/chart/values.yaml | 57 ++++++ be-rust-axum/rust-template/docker/Dockerfile | 5 + be-rust-axum/rust-template/metadata.yml | 6 + .../rust-template/release-manager.yml | 2 + be-rust-axum/rust-template/rustfmt.toml | 6 + be-rust-axum/rust-template/src/api/mod.rs | 2 + be-rust-axum/rust-template/src/api/router.rs | 63 +++++++ .../rust-template/src/api/routes/mod.rs | 1 + .../rust-template/src/api/routes/status.rs | 19 ++ be-rust-axum/rust-template/src/config/mod.rs | 1 + .../rust-template/src/config/settings.rs | 36 ++++ be-rust-axum/rust-template/src/lib.rs | 12 ++ be-rust-axum/rust-template/src/main.rs | 23 +++ be-rust-axum/rust-template/src/models/mod.rs | 1 + .../rust-template/src/models/status.rs | 6 + be-rust-axum/rust-template/tests/common.rs | 38 ++++ .../rust-template/tests/status_test.rs | 29 ++++ .../sonar-project.properties.template | 27 +++ .../testdata/golden/jenkins-build-stages.json | 42 +++++ .../golden/jenkins-provision-stages.json | 30 ++++ be-rust-axum/testdata/golden/sonar-scan.json | 25 +++ be-rust-axum/testdata/steps.yml | 25 +++ common/jenkins-agents/rust/Dockerfile.ubi8 | 22 +++ .../jenkins-agents/rust/ocp-config/Tailorfile | 5 + common/jenkins-agents/rust/ocp-config/bc.yml | 70 ++++++++ common/jenkins-agents/rust/ocp-config/is.yml | 14 ++ docs/modules/jenkins-agents/pages/rust.adoc | 22 +++ docs/modules/quickstarters/nav.adoc | 2 + .../quickstarters/pages/be-rust-axum.adoc | 162 ++++++++++++++++++ 45 files changed, 1162 insertions(+) create mode 100644 be-rust-axum/Jenkinsfile create mode 100644 be-rust-axum/Jenkinsfile.template create mode 100644 be-rust-axum/README.md create mode 100644 be-rust-axum/ocp.env create mode 100644 be-rust-axum/rust-template/.config/nextest.toml create mode 100644 be-rust-axum/rust-template/.editorconfig create mode 100644 be-rust-axum/rust-template/.gitignore create mode 100644 be-rust-axum/rust-template/.pre-commit-config.yaml create mode 100644 be-rust-axum/rust-template/Cargo.toml create mode 100644 be-rust-axum/rust-template/chart/.helmignore create mode 100644 be-rust-axum/rust-template/chart/Chart.yaml create mode 100644 be-rust-axum/rust-template/chart/templates/NOTES.txt create mode 100644 be-rust-axum/rust-template/chart/templates/_helpers.tpl create mode 100644 be-rust-axum/rust-template/chart/templates/deployment.yaml create mode 100644 be-rust-axum/rust-template/chart/templates/service.yaml create mode 100644 be-rust-axum/rust-template/chart/templates/tests/test-connection.yaml create mode 100644 be-rust-axum/rust-template/chart/values.yaml create mode 100644 be-rust-axum/rust-template/docker/Dockerfile create mode 100644 be-rust-axum/rust-template/metadata.yml create mode 100644 be-rust-axum/rust-template/release-manager.yml create mode 100644 be-rust-axum/rust-template/rustfmt.toml create mode 100644 be-rust-axum/rust-template/src/api/mod.rs create mode 100644 be-rust-axum/rust-template/src/api/router.rs create mode 100644 be-rust-axum/rust-template/src/api/routes/mod.rs create mode 100644 be-rust-axum/rust-template/src/api/routes/status.rs create mode 100644 be-rust-axum/rust-template/src/config/mod.rs create mode 100644 be-rust-axum/rust-template/src/config/settings.rs create mode 100644 be-rust-axum/rust-template/src/lib.rs create mode 100644 be-rust-axum/rust-template/src/main.rs create mode 100644 be-rust-axum/rust-template/src/models/mod.rs create mode 100644 be-rust-axum/rust-template/src/models/status.rs create mode 100644 be-rust-axum/rust-template/tests/common.rs create mode 100644 be-rust-axum/rust-template/tests/status_test.rs create mode 100644 be-rust-axum/sonar-project.properties.template create mode 100644 be-rust-axum/testdata/golden/jenkins-build-stages.json create mode 100644 be-rust-axum/testdata/golden/jenkins-provision-stages.json create mode 100644 be-rust-axum/testdata/golden/sonar-scan.json create mode 100644 be-rust-axum/testdata/steps.yml create mode 100644 common/jenkins-agents/rust/Dockerfile.ubi8 create mode 100644 common/jenkins-agents/rust/ocp-config/Tailorfile create mode 100644 common/jenkins-agents/rust/ocp-config/bc.yml create mode 100644 common/jenkins-agents/rust/ocp-config/is.yml create mode 100644 docs/modules/jenkins-agents/pages/rust.adoc create mode 100644 docs/modules/quickstarters/pages/be-rust-axum.adoc diff --git a/be-rust-axum/Jenkinsfile b/be-rust-axum/Jenkinsfile new file mode 100644 index 000000000..0e87e6c6f --- /dev/null +++ b/be-rust-axum/Jenkinsfile @@ -0,0 +1,38 @@ +def odsNamespace = '' +def odsGitRef = '' +def odsImageTag = '' +def sharedLibraryRef = '' +def agentImageTag = '' + +node { + odsNamespace = env.ODS_NAMESPACE ?: 'ods' + odsGitRef = env.ODS_GIT_REF ?: 'master' + odsImageTag = env.ODS_IMAGE_TAG ?: 'latest' + sharedLibraryRef = env.SHARED_LIBRARY_REF ?: odsImageTag + agentImageTag = env.AGENT_IMAGE_TAG ?: odsImageTag +} + +library("ods-jenkins-shared-library@${sharedLibraryRef}") + +odsQuickstarterPipeline( + imageStreamTag: "${odsNamespace}/jenkins-agent-rust:${agentImageTag}", +) { context -> + + // https://cargo-generate.github.io/cargo-generate/index.html + stage('Cargo Generate project') { + sh( + script: "cargo generate --path ${context.sourceDir}/rust-template --name ${context.projectId}-${context.componentId}", + label: "Process Rust template" + ) + sh( + script: "mv ${context.sourceDir}/${context.projectId}-${context.componentId} ${context.sourceDir}/files", + label: "Create files folder" + ) + } + + odsQuickstarterStageCopyFiles(context) + + odsQuickstarterStageRenderJenkinsfile(context) + + odsQuickstarterStageRenderSonarProperties(context) +} diff --git a/be-rust-axum/Jenkinsfile.template b/be-rust-axum/Jenkinsfile.template new file mode 100644 index 000000000..542284634 --- /dev/null +++ b/be-rust-axum/Jenkinsfile.template @@ -0,0 +1,72 @@ +// See https://www.opendevstack.org/ods-documentation/ for usage and customization. + +@Library('ods-jenkins-shared-library@@shared_library_ref@') _ + +odsComponentPipeline( + imageStreamTag: '@ods_namespace@/jenkins-agent-rust:@agent_image_tag@', + branchToEnvironmentMapping: [ + 'master': 'dev', + // 'release/': 'test' + ] +) { context -> + odsComponentFindOpenShiftImageOrElse(context) { + stageTest(context) + odsComponentStageScanWithSonar(context) + stageBuild(context) + odsComponentStageBuildOpenShiftImage(context) + } + odsComponentStageRolloutOpenShiftDeployment( + context, [ + 'selector': "app.kubernetes.io/instance=${context.componentId}", + 'helmEnvBasedValuesFiles': ["values.env.yaml"], + ] + ) +} + +def stageBuild(def context) { + stage('Cargo Build') { + sh "cargo build --release" + sh "cp -r target/release/${context.projectId}-${context.componentId} docker/app" + } +} + +def stageTest(def context) { + stage('Cargo Check') { + sh """ + cargo check --all-targets + """ + } + stage('Cargo Format') { + sh """ + cargo fmt --all -- --check + """ + } + stage('Cargo Clippy') { + sh """ + cargo clippy --all-features + + # Clippy reports for SonarQube + mkdir -p build/test-results/clippy + cargo clippy --message-format=json &> build/test-results/clippy/report.json + """ + } + stage('Cargo Test') { + sh """ + # create report folders + mkdir -p build/test-results/test + mkdir -p build/test-results/coverage + + # Tests with JUnit XML report as defined in .config/nextest.toml + cargo nextest run --profile ci + + # Coverage with LLVM lcov report from tests + # generate the lcov.info report + cargo llvm-cov --lcov --output-path ./build/test-results/coverage/lcov.info + # generate the html report + cargo llvm-cov report --html + + cp -r target/nextest/ci/results.xml build/test-results/test + cp -r target/llvm-cov/html/ build/test-results/coverage + """ + } +} diff --git a/be-rust-axum/README.md b/be-rust-axum/README.md new file mode 100644 index 000000000..6330680b7 --- /dev/null +++ b/be-rust-axum/README.md @@ -0,0 +1,7 @@ +# Rust Axum Quickstarter (be-rust-axum) + +Documentation is located in our [official documentation](https://www.opendevstack.org/ods-documentation/ods-quickstarters/latest/index.html) + +Please update documentation in the [antora page directory](https://github.com/opendevstack/ods-quickstarters/tree/master/docs/modules/ROOT/pages) + +Tested thru [automated tests](../tests/be-rust-axum) diff --git a/be-rust-axum/ocp.env b/be-rust-axum/ocp.env new file mode 100644 index 000000000..28240de6e --- /dev/null +++ b/be-rust-axum/ocp.env @@ -0,0 +1,4 @@ +MEMORY_LIMIT=128Mi +MEMORY_REQUEST=64Mi +CPU_LIMIT=100m +CPU_REQUEST=50m diff --git a/be-rust-axum/rust-template/.config/nextest.toml b/be-rust-axum/rust-template/.config/nextest.toml new file mode 100644 index 000000000..1fc6c0e5e --- /dev/null +++ b/be-rust-axum/rust-template/.config/nextest.toml @@ -0,0 +1,4 @@ +[profile.ci.junit] +path = "results.xml" +store-success-output = true +store-failure-output = true diff --git a/be-rust-axum/rust-template/.editorconfig b/be-rust-axum/rust-template/.editorconfig new file mode 100644 index 000000000..7ca4c8402 --- /dev/null +++ b/be-rust-axum/rust-template/.editorconfig @@ -0,0 +1,14 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/be-rust-axum/rust-template/.gitignore b/be-rust-axum/rust-template/.gitignore new file mode 100644 index 000000000..4eceaf099 --- /dev/null +++ b/be-rust-axum/rust-template/.gitignore @@ -0,0 +1,5 @@ +/target +/build +.vscode +.DS_Store +.env diff --git a/be-rust-axum/rust-template/.pre-commit-config.yaml b/be-rust-axum/rust-template/.pre-commit-config.yaml new file mode 100644 index 000000000..c9528f476 --- /dev/null +++ b/be-rust-axum/rust-template/.pre-commit-config.yaml @@ -0,0 +1,5 @@ +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.16.1 + hooks: + - id: gitleaks diff --git a/be-rust-axum/rust-template/Cargo.toml b/be-rust-axum/rust-template/Cargo.toml new file mode 100644 index 000000000..f4b9bc635 --- /dev/null +++ b/be-rust-axum/rust-template/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "{{project-name}}" +version = "0.1.0" +edition = "2021" +description = "{{project-name}} - from the OpenDevStack Rust QuickStarter." +license = "MIT OR Apache-2.0" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# one can add more dependencies via cargo to Cargo.toml as shown next: cargo add axum -F axum/http2 +axum = { version = "0.7", features = ["http2"] } +tokio = { version = "1.35", features = ["rt-multi-thread", "macros"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +http = "1.0" +http-body-util = "0.1" +bytes = "1.5" +thiserror = "1.0" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["tracing", "env-filter"] } +lazy_static = "1.4" + +# Nice HTTP middlewares from Tower crate, check router.rs commented code. +# Uncomment as per need, check official docs. +# tower-http = { version = "0.5", features = [ +# "trace", +# "compression-br", +# "propagate-header", +# "sensitive-headers", +# "cors", +# ] } + +# SQLx is the recommended safe and performant package to work with relational DBs like PostgreSQL; check official docs +# sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-rustls", "postgres", "uuid", "json", "time", "macros", "migrate" ] } + +# One can use envy crate for more automated env vars management, see docs. +# envy = "*" +# One can use dotenvy crate for automating ingestion of env vars from .env file. +# dotenvy = "*" + +[dev-dependencies] +tower = { version = "0.4", features = ["util"] } diff --git a/be-rust-axum/rust-template/chart/.helmignore b/be-rust-axum/rust-template/chart/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/be-rust-axum/rust-template/chart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/be-rust-axum/rust-template/chart/Chart.yaml b/be-rust-axum/rust-template/chart/Chart.yaml new file mode 100644 index 000000000..471376994 --- /dev/null +++ b/be-rust-axum/rust-template/chart/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: {{project-name}} +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.1.0" diff --git a/be-rust-axum/rust-template/chart/templates/NOTES.txt b/be-rust-axum/rust-template/chart/templates/NOTES.txt new file mode 100644 index 000000000..a21b2fa26 --- /dev/null +++ b/be-rust-axum/rust-template/chart/templates/NOTES.txt @@ -0,0 +1 @@ +Component {{ .Values.componentId }} on version {{ .Values.imageTag }} released with Helm! \ No newline at end of file diff --git a/be-rust-axum/rust-template/chart/templates/_helpers.tpl b/be-rust-axum/rust-template/chart/templates/_helpers.tpl new file mode 100644 index 000000000..7ba5edc27 --- /dev/null +++ b/be-rust-axum/rust-template/chart/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "chart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "chart.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "chart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "chart.labels" -}} +helm.sh/chart: {{ include "chart.chart" . }} +{{ include "chart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "chart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "chart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "chart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "chart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/be-rust-axum/rust-template/chart/templates/deployment.yaml b/be-rust-axum/rust-template/chart/templates/deployment.yaml new file mode 100644 index 000000000..902dc663d --- /dev/null +++ b/be-rust-axum/rust-template/chart/templates/deployment.yaml @@ -0,0 +1,78 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.componentId }} + labels: + {{- include "chart.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0% + maxSurge: 50% + selector: + matchLabels: + {{- include "chart.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "chart.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "chart.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Values.componentId }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.registry }}/{{ .Values.imageNamespace }}/{{ .Values.componentId }}:{{ .Values.imageTag }}" + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + livenessProbe: + failureThreshold: 3 + httpGet: + path: "/status" + port: {{ .Values.service.port }} + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 3 + readinessProbe: + failureThreshold: 1 + httpGet: + path: "/status" + port: {{ .Values.service.port }} + scheme: HTTP + initialDelaySeconds: 3 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 3 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/be-rust-axum/rust-template/chart/templates/service.yaml b/be-rust-axum/rust-template/chart/templates/service.yaml new file mode 100644 index 000000000..119f2ab65 --- /dev/null +++ b/be-rust-axum/rust-template/chart/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.componentId }} + labels: + {{- include "chart.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - name: {{ .Values.componentId }} + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.port }} + protocol: TCP + selector: + {{- include "chart.selectorLabels" . | nindent 4 }} diff --git a/be-rust-axum/rust-template/chart/templates/tests/test-connection.yaml b/be-rust-axum/rust-template/chart/templates/tests/test-connection.yaml new file mode 100644 index 000000000..8dfed872d --- /dev/null +++ b/be-rust-axum/rust-template/chart/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "chart.fullname" . }}-test-connection" + labels: + {{- include "chart.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "chart.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/be-rust-axum/rust-template/chart/values.yaml b/be-rust-axum/rust-template/chart/values.yaml new file mode 100644 index 000000000..5160d60f3 --- /dev/null +++ b/be-rust-axum/rust-template/chart/values.yaml @@ -0,0 +1,57 @@ +# Default values for chart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +service: + enabled: true + port: 8080 + type: ClusterIP + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "default" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 50m + memory: 64Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/be-rust-axum/rust-template/docker/Dockerfile b/be-rust-axum/rust-template/docker/Dockerfile new file mode 100644 index 000000000..9f59465c2 --- /dev/null +++ b/be-rust-axum/rust-template/docker/Dockerfile @@ -0,0 +1,5 @@ +FROM registry.access.redhat.com/ubi9/ubi-micro:latest + +COPY app /usr/local/bin/app + +CMD [ "app" ] diff --git a/be-rust-axum/rust-template/metadata.yml b/be-rust-axum/rust-template/metadata.yml new file mode 100644 index 000000000..6ae74179b --- /dev/null +++ b/be-rust-axum/rust-template/metadata.yml @@ -0,0 +1,6 @@ +--- +name: Axum - Rust web framework +description: "Axum is an ergonomic and modular web framework built with Tokio, Tower, and Hyper; written in Rust. Technologies: Axum 0.7.x, Rust 1.74.1" +supplier: https://github.com/tokio-rs/axum +version: 4.x +type: ods diff --git a/be-rust-axum/rust-template/release-manager.yml b/be-rust-axum/rust-template/release-manager.yml new file mode 100644 index 000000000..23d65c7ef --- /dev/null +++ b/be-rust-axum/rust-template/release-manager.yml @@ -0,0 +1,2 @@ +--- +dependencies: [] diff --git a/be-rust-axum/rust-template/rustfmt.toml b/be-rust-axum/rust-template/rustfmt.toml new file mode 100644 index 000000000..10b47a0f3 --- /dev/null +++ b/be-rust-axum/rust-template/rustfmt.toml @@ -0,0 +1,6 @@ +# rustfmt configurations from master branch, check rustfmt version +# (cargo fmt --version). +# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md + +edition = "2021" +tab_spaces = 2 diff --git a/be-rust-axum/rust-template/src/api/mod.rs b/be-rust-axum/rust-template/src/api/mod.rs new file mode 100644 index 000000000..b5bd1a0ce --- /dev/null +++ b/be-rust-axum/rust-template/src/api/mod.rs @@ -0,0 +1,2 @@ +pub mod router; +pub mod routes; diff --git a/be-rust-axum/rust-template/src/api/router.rs b/be-rust-axum/rust-template/src/api/router.rs new file mode 100644 index 000000000..9dee53df0 --- /dev/null +++ b/be-rust-axum/rust-template/src/api/router.rs @@ -0,0 +1,63 @@ +use axum::{Json, Router}; +use http::StatusCode; +use std::net::SocketAddr; +use tracing::info; +// See Cargo.toml tower-http dependency features +// use http::header; +// use tower_http::{ +// compression::CompressionLayer, cors::CorsLayer, propagate_header::PropagateHeaderLayer, +// sensitive_headers::SetSensitiveHeadersLayer, trace, +// }; + +use crate::api::routes; +use crate::models::status::Status; + +pub fn app() -> Router { + Router::new() + .nest( + "/v1", + // All public v1 routes will be nested here. + Router::new().merge(routes::status::create_route()), + ) + .fallback(fallback) + // Tower Middlewares - check docs for more Middlewares! + // 1. High level logging of requests and responses + // .layer( + // trace::TraceLayer::new_for_http() + // .make_span_with(trace::DefaultMakeSpan::new().include_headers(true)) + // .on_request(trace::DefaultOnRequest::new().level(tracing::Level::INFO)) + // .on_response(trace::DefaultOnResponse::new().level(tracing::Level::INFO)), + // ) + // 2. Mark the `Authorization` request header as sensitive so it doesn't + // show in logs. + // .layer(SetSensitiveHeadersLayer::new(std::iter::once( + // header::AUTHORIZATION, + // ))) + // 3. Compress responses + // .layer(CompressionLayer::new()) + // 4. Propagate `X-Request-Id`s from requests to responses + // .layer(PropagateHeaderLayer::new(header::HeaderName::from_static( + // "x-request-id", + // ))) + // 5. CORS configuration. This should probably be more restrictive in + // production. + // .layer(CorsLayer::permissive()) +} + +async fn fallback() -> (StatusCode, Json) { + ( + StatusCode::NOT_FOUND, + Json(Status { + status: "Not found".to_owned(), + }), + ) +} + +pub async fn serve(address: SocketAddr) { + let listener = tokio::net::TcpListener::bind(address).await.unwrap(); + + info!("Server listening on {}", &address); + axum::serve(listener, app().into_make_service()) + .await + .expect("Failed to start server"); +} diff --git a/be-rust-axum/rust-template/src/api/routes/mod.rs b/be-rust-axum/rust-template/src/api/routes/mod.rs new file mode 100644 index 000000000..822c7293f --- /dev/null +++ b/be-rust-axum/rust-template/src/api/routes/mod.rs @@ -0,0 +1 @@ +pub mod status; diff --git a/be-rust-axum/rust-template/src/api/routes/status.rs b/be-rust-axum/rust-template/src/api/routes/status.rs new file mode 100644 index 000000000..d26a6f0bc --- /dev/null +++ b/be-rust-axum/rust-template/src/api/routes/status.rs @@ -0,0 +1,19 @@ +use axum::{routing::get, Json, Router}; +use http::StatusCode; +use tracing::debug; + +use crate::models::status::Status; + +pub fn create_route() -> Router { + Router::new().route("/status", get(get_status)) +} + +async fn get_status() -> (StatusCode, Json) { + debug!("Returning status info"); + ( + StatusCode::OK, + Json(Status { + status: "Ok".to_owned(), + }), + ) +} diff --git a/be-rust-axum/rust-template/src/config/mod.rs b/be-rust-axum/rust-template/src/config/mod.rs new file mode 100644 index 000000000..6e98cefd0 --- /dev/null +++ b/be-rust-axum/rust-template/src/config/mod.rs @@ -0,0 +1 @@ +pub mod settings; diff --git a/be-rust-axum/rust-template/src/config/settings.rs b/be-rust-axum/rust-template/src/config/settings.rs new file mode 100644 index 000000000..ee9a40f99 --- /dev/null +++ b/be-rust-axum/rust-template/src/config/settings.rs @@ -0,0 +1,36 @@ +use lazy_static::lazy_static; +use serde::Deserialize; +use tracing::Level; + +lazy_static! { + pub static ref SETTINGS: Settings = Settings::load(); +} + +#[derive(Deserialize, Debug)] +pub struct Settings { + pub port: u16, + pub log_level: String, +} + +impl Settings { + pub fn load() -> Self { + Settings { + port: std::env::var("PORT") + .unwrap_or("8080".to_owned()) + .parse::() + .unwrap(), + log_level: std::env::var("LOG_LEVEL").unwrap_or("DEBUG".to_owned()), + } + } + + pub fn log_level(&self) -> Level { + match self.log_level.as_str() { + "ERROR" => Level::ERROR, + "WARN" => Level::WARN, + "INFO" => Level::INFO, + "DEBUG" => Level::DEBUG, + "TRACE" => Level::TRACE, + _ => Level::DEBUG, + } + } +} diff --git a/be-rust-axum/rust-template/src/lib.rs b/be-rust-axum/rust-template/src/lib.rs new file mode 100644 index 000000000..ab90f54d8 --- /dev/null +++ b/be-rust-axum/rust-template/src/lib.rs @@ -0,0 +1,12 @@ +//! # Rust boilerplate +//! +//! `{{crate_name}}` is a boilerplate implementation +//! with the [axum](https://github.com/tokio-rs/axum) framework. +//! +//! Check crate's README file to know the out of the box provided features. +/// crate containing all resources related to the API definition +pub mod api; +/// crate containing all application related configuration +pub mod config; +/// crate containing all data models and related resources +pub mod models; diff --git a/be-rust-axum/rust-template/src/main.rs b/be-rust-axum/rust-template/src/main.rs new file mode 100644 index 000000000..24cee7d4a --- /dev/null +++ b/be-rust-axum/rust-template/src/main.rs @@ -0,0 +1,23 @@ +use {{crate_name}}::api::router::serve; +use {{crate_name}}::config::settings::SETTINGS; + +use std::net::SocketAddr; +use tracing_subscriber::FmtSubscriber; + +#[tokio::main] +async fn main() { + // https://docs.rs/tracing/latest/tracing/subscriber/fn.set_global_default.html + tracing::subscriber::set_global_default( + FmtSubscriber::builder() + // all spans/events with a level higher than TRACE (e.g, debug, info, warn, etc.) + // will be written to stdout. + .with_max_level(SETTINGS.log_level()) + // completes the builder. + .finish(), + ) + .expect("setting global default subscriber failed"); + + let address = SocketAddr::from(([0, 0, 0, 0], SETTINGS.port)); + + serve(address).await; +} diff --git a/be-rust-axum/rust-template/src/models/mod.rs b/be-rust-axum/rust-template/src/models/mod.rs new file mode 100644 index 000000000..822c7293f --- /dev/null +++ b/be-rust-axum/rust-template/src/models/mod.rs @@ -0,0 +1 @@ +pub mod status; diff --git a/be-rust-axum/rust-template/src/models/status.rs b/be-rust-axum/rust-template/src/models/status.rs new file mode 100644 index 000000000..e34bf3a6d --- /dev/null +++ b/be-rust-axum/rust-template/src/models/status.rs @@ -0,0 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Status { + pub status: String, +} diff --git a/be-rust-axum/rust-template/tests/common.rs b/be-rust-axum/rust-template/tests/common.rs new file mode 100644 index 000000000..294acaa36 --- /dev/null +++ b/be-rust-axum/rust-template/tests/common.rs @@ -0,0 +1,38 @@ +use axum::body::Body; +use axum::extract::Request; +use axum::http::header::CONTENT_TYPE; +use axum::http::request; +use axum::response::Response; +use http_body_util::BodyExt; // for `collect` + +pub trait RequestBuilderExt { + fn json(self, json: serde_json::Value) -> Request; + fn empty_body(self) -> Request; +} + +impl RequestBuilderExt for request::Builder { + fn json(self, json: serde_json::Value) -> Request { + self + .header("Content-Type", "application/json") + .body(Body::from(json.to_string())) + .expect("Failed to build request") + } + fn empty_body(self) -> Request { + self.body(Body::empty()).expect("Failed to build request") + } +} + +// Generics enables us to use this method with any Type that implements Deseserialize +pub async fn response_json serde::Deserialize<'a>>(res: &mut Response) -> T { + assert_eq!( + res + .headers() + .get(CONTENT_TYPE) + .expect("expected Content-Type"), + "application/json" + ); + + let body = res.body_mut().collect().await.unwrap().to_bytes(); + + serde_json::from_slice(&body).expect("Failed to read response body as JSON") +} diff --git a/be-rust-axum/rust-template/tests/status_test.rs b/be-rust-axum/rust-template/tests/status_test.rs new file mode 100644 index 000000000..fb9652310 --- /dev/null +++ b/be-rust-axum/rust-template/tests/status_test.rs @@ -0,0 +1,29 @@ +use axum::http::{Request, StatusCode}; +use tower::ServiceExt; // for `call`, `oneshot`, and `ready` + +use {{crate_name}}::{api::router::app, models::status::Status}; + +mod common; +use common::{response_json, RequestBuilderExt}; + +#[tokio::test] +async fn get_status_route_ok() { + let app = app(); + + let mut res = app + .oneshot(Request::get("/v1/status").empty_body()) + .await + .unwrap(); + let status_code = res.status(); + let body: Status = response_json(&mut res).await; + + // Status code: + let actual = status_code; + let expected = StatusCode::OK; + assert_eq!(actual, expected); + + // Body: + let actual = body.status; + let expected = "Ok"; + assert_eq!(actual, expected); +} diff --git a/be-rust-axum/sonar-project.properties.template b/be-rust-axum/sonar-project.properties.template new file mode 100644 index 000000000..801d42a0a --- /dev/null +++ b/be-rust-axum/sonar-project.properties.template @@ -0,0 +1,27 @@ +# Project Key (required) +sonar.projectKey=@project_id@-@component_id@ + +# Project Name (optional) +sonar.projectName=@project_id@-@component_id@ + +# Comma-separated paths to directories with sources (required) +sonar.sources=src + +# Comma-separated paths to directories containing test source files. +sonar.tests=tests +sonar.test.inclusions=**/*_test.rs + +# Encoding of the source files (optional but recommended as default is ASCII) +sonar.sourceEncoding=UTF-8 + +# Exclude test files from coverage analysis +sonar.coverage.exclusions=tests/** + +# Rust Clippy reports leverages Clippy lints to raise issues against coding rules +community.rust.clippy.reportPaths=build/test-results/clippy/report.json + +# Define unit test information inside the Rust project +community.rust.test.reportPath=build/test-results/test/results.xml + +# Define coverage information inside the Rust project +community.rust.lcov.reportPaths=build/test-results/coverage/lcov.info diff --git a/be-rust-axum/testdata/golden/jenkins-build-stages.json b/be-rust-axum/testdata/golden/jenkins-build-stages.json new file mode 100644 index 000000000..e7cf4028e --- /dev/null +++ b/be-rust-axum/testdata/golden/jenkins-build-stages.json @@ -0,0 +1,42 @@ +[ + { + "stage": "odsPipeline start", + "status": "SUCCESS" + }, + { + "stage": "Cargo Check", + "status": "SUCCESS" + }, + { + "stage": "Cargo Format", + "status": "SUCCESS" + }, + { + "stage": "Cargo Clippy", + "status": "SUCCESS" + }, + { + "stage": "Cargo Test", + "status": "SUCCESS" + }, + { + "stage": "SonarQube Analysis", + "status": "SUCCESS" + }, + { + "stage": "Cargo Build", + "status": "SUCCESS" + }, + { + "stage": "Build OpenShift Image", + "status": "SUCCESS" + }, + { + "stage": "Deploy to OpenShift", + "status": "SUCCESS" + }, + { + "stage": "odsPipeline finished", + "status": "SUCCESS" + } +] diff --git a/be-rust-axum/testdata/golden/jenkins-provision-stages.json b/be-rust-axum/testdata/golden/jenkins-provision-stages.json new file mode 100644 index 000000000..5afeaf708 --- /dev/null +++ b/be-rust-axum/testdata/golden/jenkins-provision-stages.json @@ -0,0 +1,30 @@ +[ + { + "stage": "Checkout quickstarter", + "status": "SUCCESS" + }, + { + "stage": "Initialize output directory", + "status": "SUCCESS" + }, + { + "stage": "Cargo Generate project", + "status": "SUCCESS" + }, + { + "stage": "Copy files from quickstarter", + "status": "SUCCESS" + }, + { + "stage": "Create Jenkinsfile", + "status": "SUCCESS" + }, + { + "stage": "Create sonar-project.properties", + "status": "SUCCESS" + }, + { + "stage": "Push to remote", + "status": "SUCCESS" + } +] diff --git a/be-rust-axum/testdata/golden/sonar-scan.json b/be-rust-axum/testdata/golden/sonar-scan.json new file mode 100644 index 000000000..db3e70406 --- /dev/null +++ b/be-rust-axum/testdata/golden/sonar-scan.json @@ -0,0 +1,25 @@ +{ + "key": "{{.ProjectID}}-{{.ComponentID}}", + "name": "{{.ProjectID}}-{{.ComponentID}}", + "isFavorite": false, + "visibility": "public", + "extensions": [], + "qualityProfiles": [ + { + "name": "{{.SonarQualityProfile}}", + "language": "rs", + "deleted": false + } + ], + "qualityGate": { + "name": "Sonar way", + "isDefault": true + }, + "breadcrumbs": [ + { + "key": "{{.ProjectID}}-{{.ComponentID}}", + "name": "{{.ProjectID}}-{{.ComponentID}}", + "qualifier": "TRK" + } + ] +} diff --git a/be-rust-axum/testdata/steps.yml b/be-rust-axum/testdata/steps.yml new file mode 100644 index 000000000..f7755d2c6 --- /dev/null +++ b/be-rust-axum/testdata/steps.yml @@ -0,0 +1,25 @@ +componentID: python-flask-iq-test +steps: +- type: provision + provisionParams: + verify: + jenkinsStages: golden/jenkins-provision-stages.json +- type: build + buildParams: + verify: + jenkinsStages: golden/jenkins-build-stages.json + sonarScan: golden/sonar-scan.json + runAttachments: + - SCRR-{{.ProjectID}}-{{.ComponentID}}.docx + - SCRR-{{.ProjectID}}-{{.ComponentID}}.md + testResults: 1 + openShiftResources: + imageTags: + - name: "{{.ComponentID}}" + tag: latest + imageStreams: + - "{{.ComponentID}}" + deploymentConfigs: + - "{{.ComponentID}}" + services: + - "{{.ComponentID}}" diff --git a/common/jenkins-agents/rust/Dockerfile.ubi8 b/common/jenkins-agents/rust/Dockerfile.ubi8 new file mode 100644 index 000000000..3dba73f29 --- /dev/null +++ b/common/jenkins-agents/rust/Dockerfile.ubi8 @@ -0,0 +1,22 @@ +FROM opendevstackorg/ods-jenkins-agent-base-ubi8:latest + +LABEL maintainer="Gerard C.L. " + +ARG rustVersion +ARG rustToolchain + +ENV PATH="$HOME/.cargo/bin:$PATH" + +RUN yum install -y binutils cpp gcc glibc-devel glibc-headers isl kernel-headers libasan libatomic libgomp libmpc libpkgconf libubsan libxcrypt-devel llvm-libs pkgconf pkgconf-m4 pkgconf-pkg-config openssl-devel + +RUN cd /tmp && \ + curl -LfSsO https://static.rust-lang.org/dist/rust-${rustVersion}-${rustToolchain}.tar.gz && \ + tar -xzf rust-${rustVersion}-${rustToolchain}.tar.gz && \ + rm -f rust-${rustVersion}-${rustToolchain}.tar.gz && \ + cd rust-${rustVersion}-${rustToolchain} && \ + ./install.sh && \ + cargo -V && \ + cargo install cargo-nextest cargo-llvm-cov cargo-generate + +RUN chgrp -R 0 $HOME/.cargo && \ + chmod -R g=u $HOME/.cargo diff --git a/common/jenkins-agents/rust/ocp-config/Tailorfile b/common/jenkins-agents/rust/ocp-config/Tailorfile new file mode 100644 index 000000000..65789e851 --- /dev/null +++ b/common/jenkins-agents/rust/ocp-config/Tailorfile @@ -0,0 +1,5 @@ +namespace ods +selector app=jenkins-agent-rust +param-file ../../../../../ods-configuration/ods-core.env +ignore-unknown-parameters true +bc,is diff --git a/common/jenkins-agents/rust/ocp-config/bc.yml b/common/jenkins-agents/rust/ocp-config/bc.yml new file mode 100644 index 000000000..4a2f0a5e9 --- /dev/null +++ b/common/jenkins-agents/rust/ocp-config/bc.yml @@ -0,0 +1,70 @@ +apiVersion: template.openshift.io/v1 +kind: Template +metadata: + name: jenkins-agent-rust +parameters: +- name: ODS_BITBUCKET_PROJECT + description: Bitbucket project name. + value: opendevstack +- name: REPO_BASE + required: true +- name: ODS_IMAGE_TAG + required: true + value: latest +- name: ODS_GIT_REF + required: true +- name: JENKINS_AGENT_DOCKERFILE_PATH + value: Dockerfile.ubi8 + description: Dockerfile variant to use +- name: RUST_VERSION + description: "The Rust version" + required: true + value: "1.74.1" +- name: RUST_TOOLCHAIN + description: "The Rust target toolchain" + required: true + value: "x86_64-unknown-linux-gnu" +objects: +- apiVersion: v1 + kind: BuildConfig + metadata: + name: jenkins-agent-rust + labels: + app: jenkins-agent-rust + spec: + failedBuildsHistoryLimit: 5 + nodeSelector: null + output: + to: + kind: ImageStreamTag + name: jenkins-agent-python:${ODS_IMAGE_TAG} + postCommit: {} + resources: + limits: + cpu: "1" + memory: "1Gi" + requests: + cpu: "200m" + memory: "0.5Gi" + runPolicy: Serial + source: + contextDir: common/jenkins-agents/rust/docker + git: + ref: ${ODS_GIT_REF} + uri: ${REPO_BASE}/${ODS_BITBUCKET_PROJECT}/ods-quickstarters.git + sourceSecret: + name: cd-user-token + type: Git + strategy: + dockerStrategy: + dockerfilePath: ${JENKINS_AGENT_DOCKERFILE_PATH} + buildArgs: + - name: rustVersion + value: ${RUST_VERSION} + - name: rustToolchain + value: ${RUST_TOOLCHAIN} + from: + kind: ImageStreamTag + name: jenkins-agent-base:${ODS_IMAGE_TAG} + type: Docker + successfulBuildsHistoryLimit: 5 diff --git a/common/jenkins-agents/rust/ocp-config/is.yml b/common/jenkins-agents/rust/ocp-config/is.yml new file mode 100644 index 000000000..276c57e79 --- /dev/null +++ b/common/jenkins-agents/rust/ocp-config/is.yml @@ -0,0 +1,14 @@ +apiVersion: template.openshift.io/v1 +kind: Template +metadata: + name: jenkins-agent-rust +objects: +- apiVersion: v1 + kind: ImageStream + metadata: + name: jenkins-agent-rust + labels: + app: jenkins-agent-rust + spec: + lookupPolicy: + local: false diff --git a/docs/modules/jenkins-agents/pages/rust.adoc b/docs/modules/jenkins-agents/pages/rust.adoc new file mode 100644 index 000000000..7f17bec71 --- /dev/null +++ b/docs/modules/jenkins-agents/pages/rust.adoc @@ -0,0 +1,22 @@ += Rust Jenkins agent + +== Introduction +This agent is used to build / execute Rust code and tools. + +The image is built in the global `ods` project and is named `jenkins-agent-rust`. +It can be referenced in a `Jenkinsfile` with `ods/jenkins-agent-rust`. + +Minimum Supported Rust Version (MSRV) **1.74**. It can build **Rust** versions **1.74.x or higher**. + +**NOTE**: Current Rust edition in use is **2021**. See https://doc.rust-lang.org/edition-guide/introduction.html[Rust Edition Guide] for further information. + +== Features +1. https://www.rust-lang.org/[Rust] +2. https://github.com/tokio-rs/axum/[Axum] +3. https://doc.rust-lang.org/cargo/[Cargo] +4. https://nexte.st/[Nextest] +5. https://github.com/taiki-e/cargo-llvm-cov[LLVM Coverage] +6. https://cargo-generate.github.io/cargo-generate/[Generate] + +== Known limitations +No special HTTP Proxy configuration. diff --git a/docs/modules/quickstarters/nav.adoc b/docs/modules/quickstarters/nav.adoc index a544d694f..e8a0583a7 100644 --- a/docs/modules/quickstarters/nav.adoc +++ b/docs/modules/quickstarters/nav.adoc @@ -5,6 +5,7 @@ ** xref:quickstarters:be-java-springboot.adoc[BE Java/Spring Boot] ** xref:quickstarters:be-typescript-express.adoc[BE TypeScript/Express] ** xref:quickstarters:be-python-flask.adoc[BE Python/Flask] +** xref:quickstarters:be-rust-axum.adoc[BE Rust/Axum] ** xref:quickstarters:be-scala-play.adoc[BE Scala/Play] ** xref:quickstarters:fe-angular.adoc[FE Angular] ** xref:quickstarters:fe-ionic.adoc[FE Ionic] @@ -25,5 +26,6 @@ *** xref:jenkins-agents:nodejs18.adoc[Node.js 18] *** xref:jenkins-agents:nodejs20.adoc[Node.js 20] *** xref:jenkins-agents:python.adoc[Python] +*** xref:jenkins-agents:rust.adoc[Rust] *** xref:jenkins-agents:scala.adoc[Scala] ** xref:quickstarters:authoring-quickstarters.adoc[Authoring Quickstarters] diff --git a/docs/modules/quickstarters/pages/be-rust-axum.adoc b/docs/modules/quickstarters/pages/be-rust-axum.adoc new file mode 100644 index 000000000..cdb76cb60 --- /dev/null +++ b/docs/modules/quickstarters/pages/be-rust-axum.adoc @@ -0,0 +1,162 @@ += Backend Rust Axum Quickstarter (be-rust-axum) + +The project supports generation of Rust Axum based project boilerplate, and quick +installation and integration of it with OpenShift Jenkins CICD pipelines. + +== Purpose of this quickstarter + +This is a Rust project with a common Rust project folder and files structure, with its `main.rs` file for the final binary to be build, and that makes use of the `lib.rs` file, which exposes the crates (AKA modules or packages) of the project (where the business logic happens). + +The quickstarter comes with a simple API-server example written in Rust and using Axum web framework. +The package allows easily build a Rust project, using different Rust crates (packages). +It contains the basic setup for Docker, Jenkins, SonarQube and OpenShift. + +== What files / architecture is generated? + +---- +├── 📂 .config - The local Rust project config folder +│ └──  nextest.toml - The local Nextest config file (required for Jenkins CICD) +├──  .pre-commit-config.yaml - The pre-commit config file one can extend and providing gitleaks tool by default. +├── 📂 chart - The Helm chart folder +│ ├── 📂 templates - The resource files to define in your project (i.e.: deployment.yml, service.yml,...) +│ ├──  Chart.yaml - The Helm Chart main config file +│ └──  values.yaml - The values to process on your Helm chart +├── 📂 docker - The docker context to build +│ └──  Dockerfile - The docker file to deploy and run +├── Jenkinsfile - This file contains Jenkins build configuration settings +├── 📂 src +│ ├── 📂 api +│ │ ├── 📂 routes +│ │ │ ├── 🦀 mod.rs - The routes module file +│ │ │ └── 🦀 status.rs +│ │ ├── 🦀 mod.rs - The api module file +│ │ └── 🦀 router.rs - The router API routes file +│ ├── 📂 config +│ │ ├── 🦀 mod.rs - The config module file +│ │ └── 🦀 settings.rs - The settings file +│ ├── 📂 models +│ │ ├── 🦀 mod.rs - The models module file +│ │ └── 🦀 status.rs - The status model example file +│ └── 🦀 main.rs +├── 📂 target - The target folder where all builds (debug, release, ...) are stored (do not commit to git!) +├── 📂 tests +│ ├── 🦀 common.rs - Common util implementations and functions +│ └── 🦀 status_test.rs - Testing the status endpoint example +├──  Cargo.lock - The Rust dependency hash tree of this project +├──  Cargo.toml - The Rust project config file +├──  metadata.yml - Component metadata +├──  README.md - This README file +├──  release-manager.yml - Configuration file for the Release Manager +├──  rustfmt.toml +└──  sonar-project.properties - This file contains SonarQube configuration settings +---- + +== Frameworks used + +* https://blog.rust-lang.org/2023/12/07/Rust-1.74.1.html[Rust 1.74.1] +* https://github.com/tokio-rs/axum[Axum 0.7.x] + +== Usage - how do you start after you provisioned this quickstarter + +The project is production ready when deployed in OpenShift. + +Rust community and official resources are great to get to it, see +https://www.rust-lang.org/learn[learn Rust]. + +To get Rust ready on your local environment you just require installing `rustup` (see https://www.rust-lang.org/tools/install[install Rust]) + +[source,bash] +---- +# Get the Rustup CLI and already install target computer toolchain and latest stable Rust version +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# If you are new to Rust (also check the rustlings TUI) +rustup doc --book + +# Run Unit, Integration and Documentation tests +cargo test + +# Cargo format source code +cargo fmt + +# Cargo run locally +cargo run +---- + +=== Metadata + +The following are typical xref:quickstarters:metadata.adoc[metadata] values that can be used for components based on this quickstarter: +Note that the xref:jenkins-shared-library:labelling.adoc[OpenShift resources will be labeled] based on this metadata. + +```yaml +name: +description: "Some microservice implemented in Rust over Axum" +supplier: https://example.com +version: 1.0.1 +type: ods +role: backend +runtime: axum +runtimeVersion: 0.7.x +``` + +== How this quickstarter is built through Jenkins + +The Jenkinsfile is provisioned with this quick starter to ease CI/CD process. In Jenkinsfile, there are various stages: + +* *Cargo Check* - Checks we can compile: ++ +[source,bash] +---- + cargo check --all-targets +---- + +* *Cargo Format* - Checks code is properly formatted: ++ +[source,bash] +---- + cargo fmt --all -- --check +---- + +* *Cargo Clippy* - Collection of lints to catch common mistakes and improve your Rust code (output is also used on SonarQube reports): ++ +[source,bash] +---- + cargo clippy --all-features +---- + +* *Cargo Test* - Runs nextest (instead of `cargo test`) and generates xUnit (see .config/nextest.toml) and code coverage reports: ++ +[source,bash] +---- + cargo nextest run --profile ci + cargo llvm-cov --lcov +---- + +* *Build* - Builds the release target binary and moves it to the docker folder: ++ +[source,bash] +---- + cargo build --release +---- + +include::partial$secret-scanning-with-gitleaks.adoc + +== Builder agent used + +This quickstarter uses https://github.com/opendevstack/ods-quickstarters/tree/master/common/jenkins-agents/rust[Rust] Jenkins builder agent. + +**NOTE**: The ODS Jenkins Rust Agent supports Rust versions 1.74.x and above. + + +== Technologies in use + +The following Rust technologies are in use in this boilerplate: + +- https://github.com/tokio-rs/axum[Axum]: Web application framework that focuses on ergonomics and modularity. +- https://github.com/tokio-rs/tokio[Tokio]: Runtime for writing reliable, asynchronous, and slim applications. +- https://github.com/tower-rs/tower[Tower]: Library of modular and reusable components for building robust networking clients and servers. +- https://github.com/hyperium/hyper[Hyper]: A fast and correct HTTP implementation for Rust. + +== Known limitations + +Let us know if you find any, thanks!