diff --git a/.devops/deploy-pipelines.yml b/.devops/deploy-pipelines.yml new file mode 100644 index 0000000..58ed916 --- /dev/null +++ b/.devops/deploy-pipelines.yml @@ -0,0 +1,182 @@ +# Build and push image to Azure Container Registry; Deploy to Azure Kubernetes Service +# https://docs.microsoft.com/azure/devops/pipelines/languages/docker + +parameters: + - name: 'executeBuild' + displayName: 'Launch docker build' + type: boolean + default: true + +trigger: + branches: + include: + - develop + - uat + - main + paths: + include: + - src/* + - build.gradle.kts + - helm/* + - Dockerfile + - settings.gradle.kts + +pr: none + +resources: + - repo: self + +variables: + + # vmImageNameDefault: 'ubuntu-latest' + vmImageNameDefault: ubuntu-22.04 + + imageRepository: '$(K8S_IMAGE_REPOSITORY_NAME)' + deployNamespace: '$(DEPLOY_NAMESPACE)' + helmReleaseName : '$(HELM_RELEASE_NAME)' + canDeploy: true + + ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/uat') }}: + environment: 'UAT' + dockerRegistryName: '$(UAT_CONTAINER_REGISTRY_NAME)' + dockerRegistryServiceConnection: '$(UAT_CONTAINER_REGISTRY_SERVICE_CONN)' + kubernetesServiceConnection: '$(UAT_KUBERNETES_SERVICE_CONN)' + containerRegistry: '$(UAT_CONTAINER_REGISTRY_NAME)' + selfHostedAgentPool: $(UAT_AGENT_POOL) + postmanEnvFile: p4pa_UAT.postman_environment.json + + ${{ elseif eq(variables['Build.SourceBranch'], 'refs/heads/main') }}: + environment: 'PROD' + dockerRegistryName: '$(PROD_CONTAINER_REGISTRY_NAME)' + dockerRegistryServiceConnection: '$(PROD_CONTAINER_REGISTRY_SERVICE_CONN)' + kubernetesServiceConnection: '$(PROD_KUBERNETES_SERVICE_CONN)' + containerRegistry: '$(PROD_CONTAINER_REGISTRY_NAME)' + selfHostedAgentPool: $(PROD_AGENT_POOL) + postmanEnvFile: p4pa_PROD.postman_environment.json #Not used + + ${{ else }}: + environment: 'DEV' + dockerRegistryName: '$(DEV_CONTAINER_REGISTRY_NAME)' + dockerRegistryServiceConnection: '$(DEV_CONTAINER_REGISTRY_SERVICE_CONN)' + kubernetesServiceConnection: '$(DEV_KUBERNETES_SERVICE_CONN)' + containerRegistry: '$(DEV_CONTAINER_REGISTRY_NAME)' + selfHostedAgentPool: $(DEV_AGENT_POOL) + postmanEnvFile: p4pa_DEV.postman_environment.json + +stages: + - stage: stage_build + condition: eq(variables.canDeploy, true) + displayName: 'Build and publish image to ${{ variables.environment }} registry' + jobs: + - job: job_build + displayName: Build + pool: + vmImage: $(vmImageNameDefault) + steps: + - task: Bash@3 + displayName: Get app version + name: getAppVersion + condition: and(succeeded(), eq(variables.canDeploy, true)) + inputs: + targetType: 'inline' + script: | + version=$(cat build.gradle.kts | grep "version = '.*'" | cut -d"'" -f2) + echo "Building $version version" + echo "##vso[task.setvariable variable=appVersion;isOutput=true]$version" + failOnStderr: true + + - task: Docker@2 + condition: and(succeeded(), ${{ parameters.executeBuild }}) + displayName: 'Build and publish $(imageRepository) image' + inputs: + containerRegistry: '$(dockerRegistryServiceConnection)' + repository: '$(imageRepository)' + command: 'buildAndPush' + tags: | + latest + $(Build.SourceVersion) + $(getAppVersion.appVersion) + + - task: PublishPipelineArtifact@1 + displayName: 'Publish manifests into pipeline artifacts' + condition: succeeded() + inputs: + targetPath: '$(Build.Repository.LocalPath)/helm' + artifact: 'helm' + publishLocation: 'pipeline' + - task: 'Bash@3' + displayName: 'Send message on Slack' + condition: in(variables['Agent.JobStatus'], 'SucceededWithIssues', 'Failed') + inputs: + targetType: 'inline' + script: > + curl -X POST \ + -H "Content-type: application/json" \ + --data '{"text": "*Attention: There is an error in pipeline $(System.DefinitionName) in step _build_!*\nCheck the logs for more details $(System.CollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId) to view the build results."}' \ + $(SLACK_WEBHOOK_URL) + - stage: stage_deploy + displayName: 'Deploy to ${{ variables.environment }} K8S' + dependsOn: [ stage_build ] + variables: + appVersion: $[ stageDependencies.stage_build.job_build.outputs['getAppVersion.appVersion'] ] + condition: and(succeeded(), eq(variables.canDeploy, true)) + jobs: + - deployment: job_deploy + displayName: 'Deploy' + pool: + name: $(selfHostedAgentPool) + environment: '$(environment)' + strategy: + runOnce: + deploy: + steps: + - download: none + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifactName: 'helm' + targetPath: '$(Pipeline.Workspace)/helm' + - task: KubectlInstaller@0 + - task: Bash@3 + name: helm_dependency_build + displayName: Helm dependency build + inputs: + workingDirectory: '$(Pipeline.Workspace)/helm' + targetType: 'inline' + script: | + helm repo add pagopa-microservice https://pagopa.github.io/aks-microservice-chart-blueprint + helm dep build + failOnStderr: true + - bash: | + echo 'microservice-chart: + podAnnotations: + "build/buildNumber": "$(Build.BuildNumber)" + "build/appVersion": "$(appVersion)" + "build/sourceVersion": "$(Build.SourceVersion)"' > buildMetadata.yaml + displayName: Writing build metadata + + - task: HelmDeploy@0 + displayName: Helm upgrade + inputs: + kubernetesServiceEndpoint: ${{ variables.kubernetesServiceConnection }} + namespace: '$(deployNamespace)' + command: upgrade + chartType: filepath + chartPath: $(Pipeline.Workspace)/helm + chartName: ${{ variables.helmReleaseName }} + releaseName: ${{ variables.helmReleaseName }} + valueFile: "$(Pipeline.Workspace)/helm/values-${{ lower(variables.environment) }}.yaml" + install: true + waitForExecution: true + arguments: --timeout 5m0s + --values buildMetadata.yaml + - task: 'Bash@3' + displayName: 'Send message on Slack' + condition: in(variables['Agent.JobStatus'], 'SucceededWithIssues', 'Failed') + inputs: + targetType: 'inline' + script: > + curl -X POST \ + -H "Content-type: application/json" \ + --data '{"text": "*Attention: There is an error in pipeline $(System.DefinitionName) in step _deploy_!*\nCheck the logs for more details $(System.CollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId) to view the build results."}' \ + $(SLACK_WEBHOOK_URL) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..88cb251 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,30 @@ +# EditorConfig is awesome: http://EditorConfig.org +# Uses editorconfig to maintain consistent coding styles + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +max_line_length = 80 +trim_trailing_whitespace = true + +[*.{tf,tfvars}] +indent_size = 2 +indent_style = space + +[*.md] +max_line_length = 0 +trim_trailing_whitespace = false + +[Makefile] +tab_width = 2 +indent_style = tab + +[COMMIT_EDITMSG] +max_line_length = 0 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..6c08663 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Add the repository's code owners here +* @pagopa/p4pa-admins @pagopa/payments-cloud-admin diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..2b04f10 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,39 @@ +#### Description + + + + + +#### List of Changes + + +#### Motivation and Context + + +#### How Has This Been Tested? + + + +- Pre-Deploy Test + - [ ] Unit + - [ ] Integration (Narrow) +- Post-Deploy Test + - [ ] Isolated Microservice + - [ ] Broader Integration + - [ ] Acceptance + - [ ] Performance & Load + +#### Types of changes + + +- [ ] PATCH - Bug fix (backwards compatible bug fixes) +- [ ] MINOR - New feature (add functionality in a backwards compatible manner) +- [ ] MAJOR - Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] CHORE - Minor Change (fix or feature that don't impact the functionality e.g. Documentation or lint configuration) + +#### Checklist: + + + +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. diff --git a/.github/terraform/00_data.tf b/.github/terraform/00_data.tf new file mode 100644 index 0000000..83fd4f6 --- /dev/null +++ b/.github/terraform/00_data.tf @@ -0,0 +1,17 @@ +# KV Core +data "azurerm_key_vault" "key_vault_core" { + name = "${var.prefix}-${var.env_short}-${var.location_short}-core-kv" + resource_group_name = "${var.prefix}-${var.env_short}-${var.location_short}-core-sec-rg" +} + +# Kv Domain +data "azurerm_key_vault" "key_vault_domain" { + name = "${var.prefix}-${var.env_short}-${var.location_short}-${var.domain}-kv" + resource_group_name = "${var.prefix}-${var.env_short}-${var.location_short}-${var.domain}-sec-rg" +} + +# Github +data "github_organization_teams" "all" { + root_teams_only = true + summary_only = true +} diff --git a/.github/terraform/03_github_environment.tf b/.github/terraform/03_github_environment.tf new file mode 100644 index 0000000..2a4ef67 --- /dev/null +++ b/.github/terraform/03_github_environment.tf @@ -0,0 +1,86 @@ +################################# +# Repository Environment # +################################# +resource "github_repository_environment" "github_repository_environment" { + environment = var.env + repository = local.github.repository + # filter teams reviewers from github_organization_teams + # if reviewers_teams is null no reviewers will be configured for environment + dynamic "reviewers" { + for_each = (var.github_repository_environment.reviewers_teams == null || var.env_short != "p" ? [] : [1]) + content { + teams = matchkeys( + data.github_organization_teams.all.teams.*.id, + data.github_organization_teams.all.teams.*.name, + var.github_repository_environment.reviewers_teams + ) + } + } + deployment_branch_policy { + protected_branches = var.github_repository_environment.protected_branches + custom_branch_policies = var.github_repository_environment.custom_branch_policies + } +} + + +############### +# ENV Secrets # +############### + +resource "github_actions_environment_secret" "environment_secrets" { + for_each = local.env_secrets + + repository = local.github.repository + environment = var.env + secret_name = each.key + plaintext_value = each.value +} + +################# +# ENV Variables # +################# + +resource "github_actions_environment_variable" "environment_variables" { + for_each = local.env_variables + + repository = local.github.repository + environment = var.env + variable_name = each.key + value = each.value +} + +################################# +# Environment Deployment Policy # +################################# + +resource "github_repository_environment_deployment_policy" "this" { + repository = local.github.repository + environment = var.env + branch_pattern = local.map_repo[var.env] + + depends_on = [ + github_repository_environment.github_repository_environment + ] +} + +########################################## +# Environment Variable of the Repository # +########################################## +resource "github_actions_variable" "repo_env" { + for_each = var.env_short == "p" ? local.repo_env : {} + + repository = local.github.repository + variable_name = each.key + value = each.value +} + +############################# +# Secrets of the Repository # +############################# +resource "github_actions_secret" "repo_secrets" { + for_each = var.env_short == "p" ? local.repo_secrets : {} + + repository = local.github.repository + secret_name = each.key + plaintext_value = each.value +} diff --git a/.github/terraform/99_locals.tf b/.github/terraform/99_locals.tf new file mode 100644 index 0000000..835185d --- /dev/null +++ b/.github/terraform/99_locals.tf @@ -0,0 +1,36 @@ +locals { + # Common Tags: + common_tags = { + CreatedBy = "Terraform" + Environment = var.env + Owner = upper(var.prefix) + Source = "" # Repository URL + CostCenter = "" + } + + # Repo + github = { + org = "pagopa" + repository = "" # Repository Name + } + + env_secrets = { + ENV_SECRET = "data.azurerm_key_vault_secret.CHANGE_ME.value" + } + env_variables = { + ENV_VARIABLE = "ENV_VARIABLE" + } + + repo_secrets = var.env_short == "p" ? { + SECRET = "SECRET" + } : {} + repo_env = var.env_short == "p" ? { + ENV_VARIABLE = "ENV_VARIABLE" + } : {} + + map_repo = { + "dev" : "*", + "uat" : "uat" + "prod" : "main" + } +} diff --git a/.github/terraform/99_main.tf b/.github/terraform/99_main.tf new file mode 100644 index 0000000..1e567de --- /dev/null +++ b/.github/terraform/99_main.tf @@ -0,0 +1,24 @@ +terraform { + required_version = ">= 1.9" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.116" + } + github = { + source = "integrations/github" + version = "~> 6.3" + } + } + + backend "azurerm" {} +} + +provider "azurerm" { + features {} +} + +provider "github" { + owner = "pagopa" +} diff --git a/.github/terraform/99_variables.tf b/.github/terraform/99_variables.tf new file mode 100644 index 0000000..40485c1 --- /dev/null +++ b/.github/terraform/99_variables.tf @@ -0,0 +1,53 @@ +variable "prefix" { + type = string + validation { + condition = ( + length(var.prefix) <= 6 + ) + error_message = "Max length is 6 chars." + } +} + +variable "env" { + type = string + description = "Environment" +} + +variable "env_short" { + type = string + validation { + condition = ( + length(var.env_short) <= 1 + ) + error_message = "Max length is 1 chars." + } +} + +variable "location_short" { + type = string + description = "Location short like eg: neu, weu.." +} + +variable "domain" { + type = string + validation { + condition = ( + length(var.domain) <= 12 + ) + error_message = "Max length is 12 chars." + } +} + +variable "github_repository_environment" { + type = object({ + protected_branches = bool + custom_branch_policies = bool + reviewers_teams = list(string) + }) + description = "GitHub Continuous Integration roles" + default = { + protected_branches = false + custom_branch_policies = true + reviewers_teams = ["CHANGE_ME"] + } +} diff --git a/.github/terraform/env/itn-dev/backend.ini b/.github/terraform/env/itn-dev/backend.ini new file mode 100644 index 0000000..4939eae --- /dev/null +++ b/.github/terraform/env/itn-dev/backend.ini @@ -0,0 +1 @@ +subscription=CHANGE_ME diff --git a/.github/terraform/env/itn-dev/backend.tfvars b/.github/terraform/env/itn-dev/backend.tfvars new file mode 100644 index 0000000..1cb6eb0 --- /dev/null +++ b/.github/terraform/env/itn-dev/backend.tfvars @@ -0,0 +1,4 @@ +resource_group_name = "terraform-state-rg" +storage_account_name = "tfappdevCHANGE_ME" +container_name = "terraform-state" +key = "NAME_OF_REPOSITORY.tfstate" diff --git a/.github/terraform/env/itn-dev/terraform.tfvars b/.github/terraform/env/itn-dev/terraform.tfvars new file mode 100644 index 0000000..20c2f49 --- /dev/null +++ b/.github/terraform/env/itn-dev/terraform.tfvars @@ -0,0 +1,4 @@ +prefix = "" +env = "dev" +env_short = "d" +location_short = "" diff --git a/.github/terraform/env/itn-prod/backend.ini b/.github/terraform/env/itn-prod/backend.ini new file mode 100644 index 0000000..4939eae --- /dev/null +++ b/.github/terraform/env/itn-prod/backend.ini @@ -0,0 +1 @@ +subscription=CHANGE_ME diff --git a/.github/terraform/env/itn-prod/backend.tfvars b/.github/terraform/env/itn-prod/backend.tfvars new file mode 100644 index 0000000..1cb6eb0 --- /dev/null +++ b/.github/terraform/env/itn-prod/backend.tfvars @@ -0,0 +1,4 @@ +resource_group_name = "terraform-state-rg" +storage_account_name = "tfappdevCHANGE_ME" +container_name = "terraform-state" +key = "NAME_OF_REPOSITORY.tfstate" diff --git a/.github/terraform/env/itn-prod/terraform.tfvars b/.github/terraform/env/itn-prod/terraform.tfvars new file mode 100644 index 0000000..26dd449 --- /dev/null +++ b/.github/terraform/env/itn-prod/terraform.tfvars @@ -0,0 +1,3 @@ +prefix = "" +env = "prod" +env_short = "p" diff --git a/.github/terraform/env/itn-uat/backend.ini b/.github/terraform/env/itn-uat/backend.ini new file mode 100644 index 0000000..4939eae --- /dev/null +++ b/.github/terraform/env/itn-uat/backend.ini @@ -0,0 +1 @@ +subscription=CHANGE_ME diff --git a/.github/terraform/env/itn-uat/backend.tfvars b/.github/terraform/env/itn-uat/backend.tfvars new file mode 100644 index 0000000..1cb6eb0 --- /dev/null +++ b/.github/terraform/env/itn-uat/backend.tfvars @@ -0,0 +1,4 @@ +resource_group_name = "terraform-state-rg" +storage_account_name = "tfappdevCHANGE_ME" +container_name = "terraform-state" +key = "NAME_OF_REPOSITORY.tfstate" diff --git a/.github/terraform/env/itn-uat/terraform.tfvars b/.github/terraform/env/itn-uat/terraform.tfvars new file mode 100644 index 0000000..e46909e --- /dev/null +++ b/.github/terraform/env/itn-uat/terraform.tfvars @@ -0,0 +1,3 @@ +prefix = "" +env = "uat" +env_short = "u" diff --git a/.github/terraform/terraform.sh b/.github/terraform/terraform.sh new file mode 100755 index 0000000..047a751 --- /dev/null +++ b/.github/terraform/terraform.sh @@ -0,0 +1,324 @@ +#!/bin/bash +############################################################ +# Terraform script for managing infrastructure on Azure +# Fingerprint: d2hhdHlvdXdhbnQ/Cg== +############################################################ +# Global variables +# Version format x.y accepted +vers="1.11" +script_name=$(basename "$0") +git_repo="https://raw.githubusercontent.com/pagopa/eng-common-scripts/main/azure/${script_name}" +tmp_file="${script_name}.new" +# Check if the third parameter exists and is a file +if [ -n "$3" ] && [ -f "$3" ]; then + FILE_ACTION=true +else + FILE_ACTION=false +fi + +# Define functions +function clean_environment() { + rm -rf .terraform + rm tfplan 2>/dev/null + echo "cleaned!" +} + +function download_tool() { + #default value + cpu_type="intel" + os_type=$(uname) + + # only on MacOS + if [ "$os_type" == "Darwin" ]; then + cpu_brand=$(sysctl -n machdep.cpu.brand_string) + if grep -q -i "intel" <<< "$cpu_brand"; then + cpu_type="intel" + else + cpu_type="arm" + fi + fi + + echo $cpu_type + tool=$1 + git_repo="https://raw.githubusercontent.com/pagopa/eng-common-scripts/main/golang/${tool}_${cpu_type}" + if ! command -v $tool &> /dev/null; then + if ! curl -sL "$git_repo" -o "$tool"; then + echo "Error downloading ${tool}" + return 1 + else + chmod +x $tool + echo "${tool} downloaded! Please note this tool WON'T be copied in your **/bin folder for safety reasons. +You need to do it yourself!" + read -p "Press enter to continue" + + + fi + fi +} + +function extract_resources() { + TF_FILE=$1 + ENV=$2 + TARGETS="" + + # Check if the file exists + if [ ! -f "$TF_FILE" ]; then + echo "File $TF_FILE does not exist." + exit 1 + fi + + # Check if the directory exists + if [ ! -d "./env/$ENV" ]; then + echo "Directory ./env/$ENV does not exist." + exit 1 + fi + + TMP_FILE=$(mktemp) + grep -E '^resource|^module' $TF_FILE > $TMP_FILE + + while read -r line ; do + TYPE=$(echo $line | cut -d '"' -f 1 | tr -d ' ') + if [ "$TYPE" == "module" ]; then + NAME=$(echo $line | cut -d '"' -f 2) + TARGETS+=" -target=\"$TYPE.$NAME\"" + else + NAME1=$(echo $line | cut -d '"' -f 2) + NAME2=$(echo $line | cut -d '"' -f 4) + TARGETS+=" -target=\"$NAME1.$NAME2\"" + fi + done < $TMP_FILE + + rm $TMP_FILE + + echo "./terraform.sh $action $ENV $TARGETS" +} + +function help_usage() { + echo "terraform.sh Version ${vers}" + echo + echo "Usage: ./script.sh [ACTION] [ENV] [OTHER OPTIONS]" + echo "es. ACTION: init, apply, plan, etc." + echo "es. ENV: dev, uat, prod, etc." + echo + echo "Available actions:" + echo " clean Remove .terraform* folders and tfplan files" + echo " help This help" + echo " list List every environment available" + echo " update Update this script if possible" + echo " summ Generate summary of Terraform plan" + echo " tflist Generate an improved output of terraform state list" + echo " tlock Generate or update the dependency lock file" + echo " * any terraform option" +} + +function init_terraform() { + if [ -n "$env" ]; then + terraform init -reconfigure -backend-config="./env/$env/backend.tfvars" + else + echo "ERROR: no env configured!" + exit 1 + fi +} + +function list_env() { + # Check if env directory exists + if [ ! -d "./env" ]; then + echo "No environment directory found" + exit 1 + fi + + # List subdirectories under env directory + env_list=$(ls -d ./env/*/ 2>/dev/null) + + # Check if there are any subdirectories + if [ -z "$env_list" ]; then + echo "No environments found" + exit 1 + fi + + # Print the list of environments + echo "Available environments:" + for env in $env_list; do + env_name=$(echo "$env" | sed 's#./env/##;s#/##') + echo "- $env_name" + done +} + +function other_actions() { + if [ -n "$env" ] && [ -n "$action" ]; then + terraform "$action" -var-file="./env/$env/terraform.tfvars" -compact-warnings $other + else + echo "ERROR: no env or action configured!" + exit 1 + fi +} + +function state_output_taint_actions() { + if [ "$action" == "tflist" ]; then + # If 'tflist' is not installed globally and there is no 'tflist' file in the current directory, + # attempt to download the 'tflist' tool + if ! command -v tflist &> /dev/null && [ ! -f "tflist" ]; then + download_tool "tflist" + if [ $? -ne 0 ]; then + echo "Error: Failed to download tflist!!" + exit 1 + else + echo "tflist downloaded!" + fi + fi + if command -v tflist &> /dev/null; then + terraform state list | tflist + else + terraform state list | ./tflist + fi + else + terraform $action $other + fi +} + + +function parse_tfplan_option() { + # Create an array to contain arguments that do not start with '-tfplan=' + local other_args=() + + # Loop over all arguments + for arg in "$@"; do + # If the argument starts with '-tfplan=', extract the file name + if [[ "$arg" =~ ^-tfplan= ]]; then + echo "${arg#*=}" + else + # If the argument does not start with '-tfplan=', add it to the other_args array + other_args+=("$arg") + fi + done + + # Print all arguments in other_args separated by spaces + echo "${other_args[@]}" +} + +function tfsummary() { + local plan_file + plan_file=$(parse_tfplan_option "$@") + if [ -z "$plan_file" ]; then + plan_file="tfplan" + fi + action="plan" + other="-out=${plan_file}" + other_actions + if [ -n "$(command -v tf-summarize)" ]; then + tf-summarize -tree "${plan_file}" + else + echo "tf-summarize is not installed" + fi + if [ "$plan_file" == "tfplan" ]; then + rm $plan_file + fi +} + +function update_script() { + # Check if the repository was cloned successfully + if ! curl -sL "$git_repo" -o "$tmp_file"; then + echo "Error cloning the repository" + rm "$tmp_file" 2>/dev/null + return 1 + fi + + # Check if a newer version exists + remote_vers=$(sed -n '8s/vers="\(.*\)"/\1/p' "$tmp_file") + if [ "$(printf '%s\n' "$vers" "$remote_vers" | sort -V | tail -n 1)" == "$vers" ]; then + echo "The local script version is equal to or newer than the remote version." + rm "$tmp_file" 2>/dev/null + return 0 + fi + + # Check the fingerprint + local_fingerprint=$(sed -n '4p' "$0") + remote_fingerprint=$(sed -n '4p' "$tmp_file") + + if [ "$local_fingerprint" != "$remote_fingerprint" ]; then + echo "The local and remote file fingerprints do not match." + rm "$tmp_file" 2>/dev/null + return 0 + fi + + # Show the current and available versions to the user + echo "Current script version: $vers" + echo "Available script version: $remote_vers" + + # Ask the user if they want to update the script + read -rp "Do you want to update the script to version $remote_vers? (y/n): " answer + + if [ "$answer" == "y" ] || [ "$answer" == "Y" ]; then + # Replace the local script with the updated version + cp "$tmp_file" "$script_name" + chmod +x "$script_name" + rm "$tmp_file" 2>/dev/null + + echo "Script successfully updated to version $remote_vers" + else + echo "Update canceled by the user" + fi + + rm "$tmp_file" 2>/dev/null +} + +# Check arguments number +if [ "$#" -lt 1 ]; then + help_usage + exit 0 +fi + +# Parse arguments +action=$1 +env=$2 +filetf=$3 +shift 2 +other=$@ + +if [ -n "$env" ]; then + # shellcheck source=/dev/null + source "./env/$env/backend.ini" + if [ -z "$(command -v az)" ]; then + echo "az not found, cannot proceed" + exit 1 + fi + az account set -s "${subscription}" +fi + +# Call appropriate function based on action +case $action in + clean) + clean_environment + ;; + ?|help|-h) + help_usage + ;; + init) + init_terraform "$other" + ;; + list) + list_env + ;; + output|state|taint|tflist) + init_terraform + state_output_taint_actions $other + ;; + summ) + init_terraform + tfsummary "$other" + ;; + tlock) + terraform providers lock -platform=windows_amd64 -platform=darwin_amd64 -platform=darwin_arm64 -platform=linux_amd64 + ;; + update) + update_script + ;; + *) + if [ "$FILE_ACTION" = true ]; then + extract_resources "$filetf" "$env" + else + init_terraform + other_actions "$other" + fi + ;; +esac diff --git a/.github/workflows/codereview.yml b/.github/workflows/codereview.yml new file mode 100644 index 0000000..22616bd --- /dev/null +++ b/.github/workflows/codereview.yml @@ -0,0 +1,49 @@ +name: TEMPLATE-PAYMENTS - Code Review + +on: + push: + branches: + - main + - uat + - develop + pull_request: + types: + - opened + - edited + - synchronize +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 + with: + fetch-depth: 0 + + - name: Setup Java + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 #v4.2.1 + with: + distribution: 'corretto' + java-version: 21 + + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + + - name: Build with Gradle + working-directory: ./ + run: ./gradlew clean build jacocoTestReport + + - name: Sonar Scan + working-directory: ./ + run: > + ./gradlew sonar + -Dorg.gradle.jvmargs=-Xmx4096M + -Dsonar.host.url=https://sonarcloud.io + -Dsonar.organization=${{ vars.SONARCLOUD_ORG }} + -Dsonar.projectKey=${{ vars.SONARCLOUD_PROJECT_KEY }} + -Dsonar.projectName="${{ vars.SONARCLOUD_PROJECT_NAME }}" + -Dsonar.token=${{ secrets.SONAR_TOKEN }} + -Dsonar.sources=src/main + -Dsonar.tests=src/test + -Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml + -Dsonar.exclusions='**/enums/**, **/model/**, **/dto/**, **/*Constant*, **/*Config.java, **/*Scheduler.java, **/*Application.java, **/src/test/**, **/Dummy*.java' diff --git a/.github/workflows/config/trivy.yaml b/.github/workflows/config/trivy.yaml new file mode 100644 index 0000000..298b3a3 --- /dev/null +++ b/.github/workflows/config/trivy.yaml @@ -0,0 +1,3 @@ +db: + repository: "public.ecr.aws/aquasecurity/trivy-db:2" + java-repository: "public.ecr.aws/aquasecurity/trivy-java-db:1" \ No newline at end of file diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 0000000..edabda9 --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,55 @@ +name: Validate PR title + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-22.04 + steps: + # Please look up the latest version from + # https://github.com/amannn/action-semantic-pull-request/releases + - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Configure which types are allowed. + # Default: https://github.com/commitizen/conventional-commit-types + types: | + fix + feat + docs + chore + breaking + # Configure that a scope must always be provided. + requireScope: false + # Configure additional validation for the subject based on a regex. + # This example ensures the subject starts with an uppercase character. + subjectPattern: ^[A-Z].+$ + # If `subjectPattern` is configured, you can use this property to override + # the default error message that is shown when the pattern doesn't match. + # The variables `subject` and `title` can be used within the message. + subjectPatternError: | + The subject "{subject}" found in the pull request title "{title}" + didn't match the configured pattern. Please ensure that the subject + starts with an uppercase character. + # For work-in-progress PRs you can typically use draft pull requests + # from Github. However, private repositories on the free plan don't have + # this option and therefore this action allows you to opt-in to using the + # special "[WIP]" prefix to indicate this state. This will avoid the + # validation of the PR title and the pull request checks remain pending. + # Note that a second check will be reported if this is enabled. + wip: true + # When using "Squash and merge" on a PR with only one commit, GitHub + # will suggest using that commit message instead of the PR title for the + # merge commit, and it's easy to commit this by mistake. Enable this option + # to also validate the commit message for one commit PRs. + validateSingleCommit: false + # Related to `validateSingleCommit` you can opt-in to validate that the PR + # title matches a single commit to avoid confusion. + validateSingleCommitMatchesPrTitle: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..07ae6f5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,24 @@ +name: Release + +on: + # Trigger the workflow on push on the main branch + push: + branches: + - main + paths-ignore: + - 'CODEOWNERS' + - '**.md' + - '.**' + +jobs: + release: + name: Release + runs-on: ubuntu-22.04 + + steps: + + - name: πŸš€ Release with docker action + id: release + uses: pagopa/eng-github-actions-iac-template/global/release-with-docker@main # + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml new file mode 100644 index 0000000..3783154 --- /dev/null +++ b/.github/workflows/security-scan.yml @@ -0,0 +1,70 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# This workflow checks out code, builds an image, performs a container image +# vulnerability scan with Trivy tool, and integrates the results with GitHub Advanced Security +# code scanning feature. +name: Container Scan + +on: + pull_request: + # The branches below must be a subset of the branches above + branches: [ "develop", "uat", "main" ] + workflow_dispatch: + schedule: + - cron: '00 07 * * *' + +permissions: + contents: read + +jobs: + BuildAndScan: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + outputs: + CVE_CRITICAL: ${{env.CVE_CRITICAL}} + CVE_HIGH: ${{env.CVE_HIGH}} + CVE_MEDIUM: ${{env.CVE_MEDIUM}} + steps: + - name: Checkout the code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag localbuild/testimage:latest + - name: Run the Trivy scan action itself with GitHub Advanced Security code scanning integration enabled + id: scan + uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # 0.28.0 + with: + trivy-config: 'config/trivy.yaml' + image-ref: "localbuild/testimage:latest" + format: 'sarif' + output: 'results.sarif' + - name: Upload Anchore Scan Report + uses: github/codeql-action/upload-sarif@99c9897648dded3fe63d6f328c46089dd57735ca #codeql bundle v2.17.0 + with: + sarif_file: 'results.sarif' + - name: CVE Description escaped extraction and print + run: | + SCAN_RESULTS=$(jq -r 'try .runs[0].tool.driver.rules | map(.help.text) | join("\\n")' results.sarif) + echo "CVE_CRITICAL=$(echo $SCAN_RESULTS | grep -o CRITICAL | wc -l)" >> $GITHUB_ENV + echo "CVE_HIGH=$(echo $SCAN_RESULTS | grep -o HIGH | wc -l)" >> $GITHUB_ENV + echo "CVE_MEDIUM=$(echo $SCAN_RESULTS | grep -o MEDIUM | wc -l)" >> $GITHUB_ENV + + echo $SCAN_RESULTS + - name: Fails if CVE HIGH or CRITICAL are detected + id: cve-threshold + if: env.CVE_HIGH > 0 || env.CVE_CRITICAL > 0 + run: exit 1 + SendSlackNotification: + needs: BuildAndScan + uses: ./.github/workflows/send-notification.yml + if: always() && (needs.BuildAndScan.outputs.CVE_HIGH > 0 || needs.BuildAndScan.outputs.CVE_CRITICAL > 0) + with: + CVE_CRITICAL: ${{needs.BuildAndScan.outputs.CVE_CRITICAL}} + CVE_HIGH: ${{needs.BuildAndScan.outputs.CVE_HIGH}} + CVE_MEDIUM: ${{needs.BuildAndScan.outputs.CVE_MEDIUM}} + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/send-notification.yml b/.github/workflows/send-notification.yml new file mode 100644 index 0000000..5820a14 --- /dev/null +++ b/.github/workflows/send-notification.yml @@ -0,0 +1,49 @@ +name: "Send notification" + +on: + workflow_call: + inputs: + CVE_CRITICAL: + required: true + type: string + CVE_HIGH: + required: true + type: string + CVE_MEDIUM: + required: true + type: string + secrets: + SLACK_WEBHOOK_URL: + required: true + +jobs: + Notify: + name: Notify Slack + runs-on: ubuntu-latest + steps: + - name: Send notification to Slack + id: slack + uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 #v1.25.0 + with: + payload: | + { + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "[ ${{ github.event.repository.name }} ]" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": " `CRITICAL` : *${{ inputs.CVE_CRITICAL }}*\n\n`HIGH` : *${{ inputs.CVE_HIGH }}*\n\n`MEDIUM` : *${{ inputs.CVE_MEDIUM }}*\n\n" + } + } + ] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK \ No newline at end of file diff --git a/.github/workflows/snapshot-docker.yml b/.github/workflows/snapshot-docker.yml new file mode 100644 index 0000000..3496adf --- /dev/null +++ b/.github/workflows/snapshot-docker.yml @@ -0,0 +1,23 @@ +name: Snapshot docker build and push + +on: + push: + # Sequence of patterns matched against refs/heads + branches-ignore: + - 'main' + paths-ignore: + - 'CODEOWNERS' + - '**.md' + - '.**' + +jobs: + release: + name: Snapshot Docker + runs-on: ubuntu-22.04 + + steps: + - name: πŸ“¦ Docker build and push + id: release + uses: pagopa/eng-github-actions-iac-template/global/docker-build-push@main # + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1a6165 --- /dev/null +++ b/.gitignore @@ -0,0 +1,132 @@ +HELP.md +!**/src/main/**/target/ +!**/src/test/**/target/ + +#**/src/main/resources/application-local*.properties + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +###################### +# Maven +###################### +/log/ +/target/** +**/target/** +###################### +# Gradle +###################### +.gradle/ +/build/ + +###################### +# Package Files +###################### +*.jar +*.war +*.ear +*.db + +###################### +# Windows +###################### +# Windows image file caches +Thumbs.db + +# Folder config file +Desktop.ini + +###################### +# Mac OSX +###################### +.DS_Store +.svn + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +###################### +# Directories +###################### +/bin/ +/deploy/ + +###################### +# Logs +###################### +*.log* + +###################### +# Others +###################### +*.class +*.*~ +*~ +.merge_file* + +###################### +# Gradle Wrapper +###################### +!gradle/wrapper/gradle-wrapper.jar + +###################### +# Maven Wrapper +###################### +!.mvn/wrapper/maven-wrapper.jar + +###################### +# ESLint +###################### +.eslintcache + +# Application resources +*/src/main/resources/application-local*.properties +*/src/main/resources/application-local*.yml +*/src/main/resources/application-local*.env + + +# Test jUnit +/output/ + +# Helm charts +*/helm/charts + +*/.java-version + +### Terraform ### +**/.terraform/* +*.tfstate +*.tfstate.* + +### Local Sonar ### +**.scannerwork +**/sonar-project.properties diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f023d52 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,27 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: c4a0b883114b00d8d76b479c820ce7950211c99b # v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-json + - id: check-added-large-files + - repo: https://github.com/antonbabenko/pre-commit-terraform + rev: e87ee4371c9f09daac814845df196a65cac28a7a # v1.96.2 + hooks: + - id: terraform_fmt + - id: terraform_docs + args: + - markdown --sort-by required + - id: terraform_validate + args: + - --init-args=-lockfile=readonly + - --args=-json + - --args=-no-color +# - id: terraform_providers_lock +# args: +# - --args=-platform=windows_amd64 +# - --args=-platform=darwin_amd64 +# - --args=-platform=darwin_arm64 +# - --args=-platform=linux_amd64 +# - --args=-platform=linux_arm64 diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..d1d92ca --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,13 @@ +{ + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "angular", + "releaseRules": [{ "type": "breaking", "release": "major" }] + } + ], + "@semantic-release/release-notes-generator", + "@semantic-release/github" + ] +} diff --git a/.terraform-version b/.terraform-version new file mode 100644 index 0000000..158c747 --- /dev/null +++ b/.terraform-version @@ -0,0 +1 @@ +1.9.5 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cee3b16 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,156 @@ +# syntax=docker/dockerfile:1.4@sha256:9ba7531bd80fb0a858632727cf7a112fbfd19b17e94c4e84ced81e24ef1a0dbc + +# +# 🎯 Version Management +# +ARG CORRETTO_VERSION="21-alpine3.20" +ARG CORRETTO_SHA="8b16834e7fabfc62d4c8faa22de5df97f99627f148058d52718054aaa4ea3674" +ARG GRADLE_VERSION="8.10.2" +ARG GRADLE_DOWNLOAD_SHA256="31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26" +ARG APPINSIGHTS_VERSION="3.6.2" + +# 🌍 Timezone Configuration +ARG TZ="Europe/Rome" + +# πŸ”§ Build Configuration +ARG GRADLE_OPTS="-Dorg.gradle.daemon=false \ + -Dorg.gradle.parallel=true \ + -Dorg.gradle.caching=true \ + -Dorg.gradle.configureondemand=true \ + -Dorg.gradle.jvmargs=-Xmx2g" + +# πŸ‘€ App Configuration +ARG APP_USER="appuser" +ARG APP_GROUP="appgroup" +ARG APP_HOME="/app" +ARG GRADLE_HOME="/opt/gradle" + +# +# πŸ“₯ Base Setup Stage +# +FROM amazoncorretto:${CORRETTO_VERSION}@sha256:${CORRETTO_SHA} AS base +ARG APP_USER +ARG APP_GROUP + +# Install base packages +RUN apk add --no-cache \ + wget \ + unzip \ + bash \ + shadow + +# Create Gradle user +RUN groupadd --system --gid 1000 ${APP_GROUP} && \ + useradd --system --gid ${APP_GROUP} --uid 1000 --shell /bin/bash --create-home ${APP_USER} + +# +# πŸ“¦ Gradle Setup Stage +# +FROM base AS gradle-setup +ARG GRADLE_VERSION +ARG GRADLE_DOWNLOAD_SHA256 +ARG GRADLE_HOME +ARG GRADLE_OPTS +ARG APP_USER +ARG APP_GROUP + +# Set environment variables for Gradle +ENV GRADLE_OPTS="${GRADLE_OPTS}" +ENV GRADLE_HOME="${GRADLE_HOME}" +ENV PATH="${GRADLE_HOME}/bin:${PATH}" + +WORKDIR /tmp + +# Download and verify Gradle with progress bar +RUN echo "Downloading Gradle ${GRADLE_VERSION}..." && \ + wget --progress=bar:force --output-document=gradle.zip \ + "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" && \ + echo "Verifying download..." && \ + echo "${GRADLE_DOWNLOAD_SHA256} gradle.zip" | sha256sum -c - && \ + echo "Installing Gradle..." && \ + unzip -q gradle.zip && \ + mv "gradle-${GRADLE_VERSION}" "${GRADLE_HOME}" && \ + ln -s "${GRADLE_HOME}/bin/gradle" /usr/bin/gradle && \ + rm gradle.zip && \ + # Setup Gradle user directories + mkdir -p /home/${APP_USER}/.gradle && \ + chown --recursive ${APP_USER}:${APP_GROUP} /home/${APP_USER} && \ + # Verify installation + echo "Verifying Gradle installation..." && \ + gradle --version + +# Create Gradle volume +VOLUME /home/${APP_USER}/.gradle + +# +# πŸ“š Dependencies Stage +# +FROM gradle-setup AS dependencies + +WORKDIR /build + +# Copy build configuration +COPY --chown=${APP_USER}:${APP_GROUP} build.gradle.kts settings.gradle.kts ./ +COPY --chown=${APP_USER}:${APP_GROUP} gradle.lockfile ./ +COPY --chown=${APP_USER}:${APP_GROUP} openapi openapi/ + +# Generate OpenAPI stubs and download dependencies +RUN mkdir -p src/main/java && \ + chown -R ${APP_USER}:${APP_GROUP} /build && \ + chmod -R 775 /build + +USER ${APP_USER} + +RUN gradle openApiGenerate dependencies --no-daemon + +# +# πŸ—οΈ Build Stage +# +FROM dependencies AS build + +# Copy source code +COPY --chown=${APP_USER}:${APP_GROUP} src src/ + +# Build application +RUN gradle bootJar --no-daemon + +# +# πŸš€ Runtime Stage +# +FROM amazoncorretto:${CORRETTO_VERSION}@sha256:${CORRETTO_SHA} AS runtime +ARG APP_USER +ARG APP_GROUP +ARG APP_HOME +ARG APPINSIGHTS_VERSION +ARG TZ + +WORKDIR ${APP_HOME} + +# Set timezone environment variable +ENV TZ=${TZ} + +# πŸ›‘οΈ Security Setup and Timezone +RUN apk upgrade --no-cache && \ + apk add --no-cache \ + tini \ + curl \ + # Configure timezone + ENV=TZ + tzdata && \ + # Create user and group + addgroup -S ${APP_GROUP} && \ + adduser -S ${APP_USER} -G ${APP_GROUP} + +# πŸ“¦ Copy Artifacts +COPY --from=build /build/build/libs/*.jar ${APP_HOME}/app.jar +ADD --chmod=644 https://github.com/microsoft/ApplicationInsights-Java/releases/download/${APPINSIGHTS_VERSION}/applicationinsights-agent-${APPINSIGHTS_VERSION}.jar ${APP_HOME}/applicationinsights-agent.jar + +# πŸ“ Set Permissions +RUN chown -R ${APP_USER}:${APP_GROUP} ${APP_HOME} + +# πŸ”Œ Container Configuration +EXPOSE 8080 +USER ${APP_USER} + +# 🎬 Startup Configuration +ENTRYPOINT ["/sbin/tini", "--"] +CMD ["java", "-jar", "/app/app.jar"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..83abf99 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Example Repository Template + +This repository serves as an **example template** to kick-start your projects with pre-configured files and folders for **OpenAPI**, **Helm**, **Gradle**, **Java**, and **JUnit testing**. It is designed to streamline the initial setup of new projects and ensure consistency in project structure. + +--- + +## πŸ“‚ Repository Structure + +Here is a quick overview of the files and directories included in this repository: + +```plaintext +. +β”œβ”€β”€ .devops/ # DevOps pipelines +β”œβ”€β”€ .github/ # GitHub configuration files +β”œβ”€β”€ gradle/ # Gradle wrapper files +β”œβ”€β”€ helm/ # Helm charts for Kubernetes deployments +β”œβ”€β”€ openapi/ # OpenAPI specification files +β”œβ”€β”€ src/ # Source code for the Java application +β”‚ β”œβ”€β”€ main/ +β”‚ └── test/ +β”œβ”€β”€ build.gradle.kts # Gradle build file +β”œβ”€β”€ Dockerfile # Docker build file +β”œβ”€β”€ README.md # Project documentation +β”œβ”€β”€ settings.gradle.kts # Gradle settings file +└── .gitignore # Git ignore rules +``` + +## πŸš€ Features + +### πŸ“œ OpenAPI +- Example OpenAPI specification file (`template-payments-java-repository.openapi.yaml`) to document your RESTful APIs. +- Compatible with tools like Swagger and Postman. + +### βš™οΈ Helm +- Template Helm charts for deploying your Java application on Kubernetes. +- Includes `values.yaml` for parameter configuration and pre-defined deployment manifests. + +### πŸ”§ Gradle +- `build.gradle` file with dependencies and plugins for building, testing, and running your Java application. +- Compatible with Java 21+. + +### β˜• Java +- Example Java application structure with a simple `HelloWorld` class. + +### βœ… JUnit +- Example JUnit test cases under the `test/` directory to help you get started with unit testing. + +--- + +## πŸ› οΈ Getting Started + +### Prerequisites +Ensure the following tools are installed on your machine: +1. **Java 21+** +2. **Gradle** (or use the Gradle wrapper included in the repository) +3. **Docker** (for Helm-related tasks, optional) diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..99f206f --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,114 @@ +plugins { + java + id("org.springframework.boot") version "3.4.0" + id("io.spring.dependency-management") version "1.1.6" + jacoco + id("org.sonarqube") version "6.0.1.5171" + id("com.github.ben-manes.versions") version "0.51.0" + id("org.openapi.generator") version "7.10.0" +} + +group = "it.gov.pagopa.payhub" +version = "0.0.1" +description = "template-payments-java-repository" + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +configurations { + compileOnly { + extendsFrom(configurations.annotationProcessor.get()) + } +} + +repositories { + mavenCentral() +} + +val springDocOpenApiVersion = "2.7.0" +val openApiToolsVersion = "0.2.6" +val micrometerVersion = "1.4.0" + +dependencies { + implementation("org.springframework.boot:spring-boot-starter") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-actuator") + implementation("io.micrometer:micrometer-tracing-bridge-otel:$micrometerVersion") + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:$springDocOpenApiVersion") + implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") + implementation("org.openapitools:jackson-databind-nullable:$openApiToolsVersion") + + compileOnly("org.projectlombok:lombok") + annotationProcessor("org.projectlombok:lombok") + + // Testing + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.mockito:mockito-core") + testImplementation ("org.projectlombok:lombok") +} + +tasks.withType { + useJUnitPlatform() + finalizedBy(tasks.jacocoTestReport) +} + +tasks.jacocoTestReport { + dependsOn(tasks.test) + reports { + xml.required = true + } +} + +val projectInfo = mapOf( + "artifactId" to project.name, + "version" to project.version +) + +tasks { + val processResources by getting(ProcessResources::class) { + filesMatching("**/application.yml") { + expand(projectInfo) + } + } +} + +configurations { + compileClasspath { + resolutionStrategy.activateDependencyLocking() + } +} + +tasks.compileJava { + dependsOn("openApiGenerate") +} + +configure { + named("main") { + java.srcDir("$projectDir/build/generated/src/main/java") + } +} + +springBoot { + mainClass.value("it.gov.pagopa.template.TemplateApplication") +} + +openApiGenerate { + generatorName.set("spring") + inputSpec.set("$rootDir/openapi/template-payments-java-repository.openapi.yaml") + outputDir.set("$projectDir/build/generated") + apiPackage.set("it.gov.pagopa.template.controller.generated") + modelPackage.set("it.gov.pagopa.template.dto.generated") + configOptions.set(mapOf( + "dateLibrary" to "java8", + "requestMappingMode" to "api_interface", + "useSpringBoot3" to "true", + "interfaceOnly" to "true", + "useTags" to "true", + "generateConstructorWithAllArgs" to "false", + "generatedConstructorWithRequiredArgs" to "true", + "additionalModelTypeAnnotations" to "@lombok.Data @lombok.Builder @lombok.AllArgsConstructor" + )) +} diff --git a/gradle.lockfile b/gradle.lockfile new file mode 100644 index 0000000..b2c9d73 --- /dev/null +++ b/gradle.lockfile @@ -0,0 +1,75 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +aopalliance:aopalliance:1.0=compileClasspath +ch.qos.logback:logback-classic:1.5.12=compileClasspath +ch.qos.logback:logback-core:1.5.12=compileClasspath +com.fasterxml.jackson.core:jackson-annotations:2.18.1=compileClasspath +com.fasterxml.jackson.core:jackson-core:2.18.1=compileClasspath +com.fasterxml.jackson.core:jackson-databind:2.18.1=compileClasspath +com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.1=compileClasspath +com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.1=compileClasspath +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.1=compileClasspath +com.fasterxml.jackson.module:jackson-module-parameter-names:2.18.1=compileClasspath +com.fasterxml.jackson:jackson-bom:2.18.1=compileClasspath +io.micrometer:context-propagation:1.1.2=compileClasspath +io.micrometer:micrometer-commons:1.14.1=compileClasspath +io.micrometer:micrometer-core:1.14.1=compileClasspath +io.micrometer:micrometer-jakarta9:1.14.1=compileClasspath +io.micrometer:micrometer-observation:1.14.1=compileClasspath +io.micrometer:micrometer-tracing-bridge-otel:1.4.0=compileClasspath +io.micrometer:micrometer-tracing:1.4.0=compileClasspath +io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.9.0-alpha=compileClasspath +io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.9.0=compileClasspath +io.opentelemetry.semconv:opentelemetry-semconv:1.25.0-alpha=compileClasspath +io.opentelemetry:opentelemetry-api:1.43.0=compileClasspath +io.opentelemetry:opentelemetry-context:1.43.0=compileClasspath +io.opentelemetry:opentelemetry-extension-trace-propagators:1.43.0=compileClasspath +io.opentelemetry:opentelemetry-sdk-common:1.43.0=compileClasspath +io.opentelemetry:opentelemetry-sdk-logs:1.43.0=compileClasspath +io.opentelemetry:opentelemetry-sdk-metrics:1.43.0=compileClasspath +io.opentelemetry:opentelemetry-sdk-trace:1.43.0=compileClasspath +io.opentelemetry:opentelemetry-sdk:1.43.0=compileClasspath +io.swagger.core.v3:swagger-annotations-jakarta:2.2.25=compileClasspath +io.swagger.core.v3:swagger-core-jakarta:2.2.25=compileClasspath +io.swagger.core.v3:swagger-models-jakarta:2.2.25=compileClasspath +jakarta.activation:jakarta.activation-api:2.1.3=compileClasspath +jakarta.annotation:jakarta.annotation-api:2.1.1=compileClasspath +jakarta.validation:jakarta.validation-api:3.0.2=compileClasspath +jakarta.xml.bind:jakarta.xml.bind-api:4.0.2=compileClasspath +org.apache.commons:commons-lang3:3.17.0=compileClasspath +org.apache.logging.log4j:log4j-api:2.24.1=compileClasspath +org.apache.logging.log4j:log4j-to-slf4j:2.24.1=compileClasspath +org.apache.tomcat.embed:tomcat-embed-core:10.1.33=compileClasspath +org.apache.tomcat.embed:tomcat-embed-el:10.1.33=compileClasspath +org.apache.tomcat.embed:tomcat-embed-websocket:10.1.33=compileClasspath +org.jspecify:jspecify:1.0.0=compileClasspath +org.openapitools:jackson-databind-nullable:0.2.6=compileClasspath +org.projectlombok:lombok:1.18.36=compileClasspath +org.slf4j:jul-to-slf4j:2.0.16=compileClasspath +org.slf4j:slf4j-api:2.0.16=compileClasspath +org.springdoc:springdoc-openapi-starter-common:2.7.0=compileClasspath +org.springdoc:springdoc-openapi-starter-webmvc-api:2.7.0=compileClasspath +org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0=compileClasspath +org.springframework.boot:spring-boot-actuator-autoconfigure:3.4.0=compileClasspath +org.springframework.boot:spring-boot-actuator:3.4.0=compileClasspath +org.springframework.boot:spring-boot-autoconfigure:3.4.0=compileClasspath +org.springframework.boot:spring-boot-starter-actuator:3.4.0=compileClasspath +org.springframework.boot:spring-boot-starter-json:3.4.0=compileClasspath +org.springframework.boot:spring-boot-starter-logging:3.4.0=compileClasspath +org.springframework.boot:spring-boot-starter-tomcat:3.4.0=compileClasspath +org.springframework.boot:spring-boot-starter-web:3.4.0=compileClasspath +org.springframework.boot:spring-boot-starter:3.4.0=compileClasspath +org.springframework.boot:spring-boot:3.4.0=compileClasspath +org.springframework:spring-aop:6.2.0=compileClasspath +org.springframework:spring-beans:6.2.0=compileClasspath +org.springframework:spring-context:6.2.0=compileClasspath +org.springframework:spring-core:6.2.0=compileClasspath +org.springframework:spring-expression:6.2.0=compileClasspath +org.springframework:spring-jcl:6.2.0=compileClasspath +org.springframework:spring-web:6.2.0=compileClasspath +org.springframework:spring-webmvc:6.2.0=compileClasspath +org.webjars:swagger-ui:5.18.2=compileClasspath +org.webjars:webjars-locator-lite:1.0.1=compileClasspath +org.yaml:snakeyaml:2.3=compileClasspath +empty= diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..a4b76b9 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ccc1a9b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..f5feea6 --- /dev/null +++ b/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright Β© 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions Β«$varΒ», Β«${var}Β», Β«${var:-default}Β», Β«${var+SET}Β», +# Β«${var#prefix}Β», Β«${var%suffix}Β», and Β«$( cmd )Β»; +# * compound commands having a testable exit status, especially Β«caseΒ»; +# * various built-in commands including Β«commandΒ», Β«setΒ», and Β«ulimitΒ». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/helm/.helmignore b/helm/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/.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/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 0000000..bdec32b --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: template-payments-java-repository +description: TEMPLATE PAYMENTS JAVA REPOSITORY +type: application +version: 1.0.0 +appVersion: 1.0.0 +dependencies: + - name: microservice-chart + version: 5.9.0 + repository: "https://pagopa.github.io/aks-microservice-chart-blueprint" diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml new file mode 100644 index 0000000..54229d4 --- /dev/null +++ b/helm/values-dev.yaml @@ -0,0 +1,36 @@ +microservice-chart: + image: + repository: p4paditncorecommonacr.azurecr.io/templatepaymentsjavarepository + tag: latest + pullPolicy: Always + + ingress: + host: "hub.internal.dev.p4pa.pagopa.it" + + resources: + requests: + memory: "256Mi" + cpu: "40m" + limits: + memory: "4Gi" + cpu: "300m" + + autoscaling: + enable: false + # minReplica: 1 + # maxReplica: 1 + # pollingInterval: 30 # seconds + # cooldownPeriod: 300 # seconds + # triggers: + # - type: cpu + # metadata: + # type: Utilization # Allowed types are 'Utilization' or 'AverageValue' + # value: "70" + + envConfig: + ENV: "DEV" + JAVA_TOOL_OPTIONS: "-Xms128m -Xmx4g -Djava.util.concurrent.ForkJoinPool.common.parallelism=7 -javaagent:/app/applicationinsights-agent.jar -Dapplicationinsights.configuration.file=/mnt/file-config-external/appinsights-config/applicationinsights.json -agentlib:jdwp=transport=dt_socket,server=y,address=8001,suspend=n -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=3002 -Dcom.sun.management.jmxremote.rmi.port=3003 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" + + keyvault: + name: "p4pa-d-payhub-kv" + tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml new file mode 100644 index 0000000..4bee648 --- /dev/null +++ b/helm/values-prod.yaml @@ -0,0 +1,36 @@ +microservice-chart: + image: + repository: p4papitncorecommonacr.azurecr.io/templatepaymentsjavarepository + tag: latest + pullPolicy: Always + + ingress: + host: "hub.internal.p4pa.pagopa.it" + + resources: + requests: + memory: "256Mi" + cpu: "40m" + limits: + memory: "4Gi" + cpu: "300m" + + autoscaling: + enable: false + # minReplica: 1 + # maxReplica: 1 + # pollingInterval: 30 # seconds + # cooldownPeriod: 300 # seconds + # triggers: + # - type: cpu + # metadata: + # type: Utilization # Allowed types are 'Utilization' or 'AverageValue' + # value: "70" + + envConfig: + ENV: "PROD" + JAVA_TOOL_OPTIONS: "-Xms128m -Xmx4g -Djava.util.concurrent.ForkJoinPool.common.parallelism=7 -javaagent:/app/applicationinsights-agent.jar -Dapplicationinsights.configuration.file=/mnt/file-config-external/appinsights-config/applicationinsights.json -agentlib:jdwp=transport=dt_socket,server=y,address=8001,suspend=n -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=3002 -Dcom.sun.management.jmxremote.rmi.port=3003 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" + + keyvault: + name: "p4pa-p-payhub-kv" + tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml new file mode 100644 index 0000000..05c9d0e --- /dev/null +++ b/helm/values-uat.yaml @@ -0,0 +1,36 @@ +microservice-chart: + image: + repository: p4pauitncorecommonacr.azurecr.io/templatepaymentsjavarepository + tag: latest + pullPolicy: Always + + ingress: + host: "hub.internal.uat.p4pa.pagopa.it" + + resources: + requests: + memory: "256Mi" + cpu: "40m" + limits: + memory: "4Gi" + cpu: "300m" + + autoscaling: + enable: false + # minReplica: 1 + # maxReplica: 1 + # pollingInterval: 30 # seconds + # cooldownPeriod: 300 # seconds + # triggers: + # - type: cpu + # metadata: + # type: Utilization # Allowed types are 'Utilization' or 'AverageValue' + # value: "70" + + envConfig: + ENV: "UAT" + JAVA_TOOL_OPTIONS: "-Xms128m -Xmx4g -Djava.util.concurrent.ForkJoinPool.common.parallelism=7 -javaagent:/app/applicationinsights-agent.jar -Dapplicationinsights.configuration.file=/mnt/file-config-external/appinsights-config/applicationinsights.json -agentlib:jdwp=transport=dt_socket,server=y,address=8001,suspend=n -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=3002 -Dcom.sun.management.jmxremote.rmi.port=3003 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" + + keyvault: + name: "p4pa-u-payhub-kv" + tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 0000000..d6396f8 --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,72 @@ +microservice-chart: + namespace: "payhub" + nameOverride: "" + fullnameOverride: "" + + livenessProbe: + httpGet: + path: /actuator/health/liveness + port: 8080 + initialDelaySeconds: 120 + failureThreshold: 15 + periodSeconds: 10 + + readinessProbe: + httpGet: + path: /actuator/health/readiness + port: 8080 + initialDelaySeconds: 120 + failureThreshold: 15 + periodSeconds: 10 + + deployment: + create: true + + service: + create: true + type: ClusterIP + port: 8080 + + ingress: + create: true + path: /templatepaymentsjavarepository/(.*) + + serviceAccount: + create: false + annotations: {} + name: "" + + podAnnotations: {} + + podSecurityContext: + seccompProfile: + type: RuntimeDefault + + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + + externalConfigMapFiles: + create: true + configMaps: + - name: appinsights-config + key: applicationinsights.json + + envConfig: + APPLICATIONINSIGHTS_ROLE_NAME: "templatepaymentsjavarepository" + APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL: "OFF" + APPLICATIONINSIGHTS_INSTRUMENTATION_MICROMETER_ENABLED: "false" + APPLICATIONINSIGHTS_PREVIEW_PROFILER_ENABLED: "false" + ENABLE_AUDIT_APPENDER: "TRUE" + + + envSecret: + APPLICATIONINSIGHTS_CONNECTION_STRING: appinsights-connection-string + + # nodeSelector: {} + + # tolerations: [] + + # affinity: {} diff --git a/openapi/template-payments-java-repository.openapi.yaml b/openapi/template-payments-java-repository.openapi.yaml new file mode 100644 index 0000000..7f5b1c0 --- /dev/null +++ b/openapi/template-payments-java-repository.openapi.yaml @@ -0,0 +1,31 @@ +openapi: 3.1.0 +info: + title: Fake API + description: "Sample API" + version: "1.0.0" +paths: + /api/v1/greet: + get: + summary: "Hello World" + description: "Sample endpoint that return greetings" + responses: + '200': + description: "Success" + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: "Hello, World!" + '500': + description: "Internal Server Error" + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Internal Server Error" diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..5210869 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "template-payments-java-repository" diff --git a/src/main/java/it/gov/pagopa/template/TemplateApplication.java b/src/main/java/it/gov/pagopa/template/TemplateApplication.java new file mode 100644 index 0000000..42b42d3 --- /dev/null +++ b/src/main/java/it/gov/pagopa/template/TemplateApplication.java @@ -0,0 +1,13 @@ +package it.gov.pagopa.template; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TemplateApplication { + + public static void main(String[] args) { + SpringApplication.run(TemplateApplication.class, args); + } + +} diff --git a/src/main/java/it/gov/pagopa/template/config/SwaggerConfig.java b/src/main/java/it/gov/pagopa/template/config/SwaggerConfig.java new file mode 100644 index 0000000..350b9a6 --- /dev/null +++ b/src/main/java/it/gov/pagopa/template/config/SwaggerConfig.java @@ -0,0 +1,41 @@ +package it.gov.pagopa.template.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * The Class SwaggerConfig. + */ +@Configuration +public class SwaggerConfig { + + /** The title. */ + private final String title; + + /** The description. */ + private final String description; + + /** The version. */ + private final String version; + + public SwaggerConfig( + @Value("${swagger.title:${spring.application.name}}") String title, + @Value("${swagger.description:Api and Models}") String description, + @Value("${swagger.version:${spring.application.version}}") String version) { + this.title = title; + this.description = description; + this.version = version; + } + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI().components(new Components()).info(new Info() + .title(title) + .description(description) + .version(version)); + } +} diff --git a/src/main/java/it/gov/pagopa/template/utils/Calculator.java b/src/main/java/it/gov/pagopa/template/utils/Calculator.java new file mode 100644 index 0000000..5906f6c --- /dev/null +++ b/src/main/java/it/gov/pagopa/template/utils/Calculator.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.template.utils; + +public class Calculator { + + public int multiply(int a, int b) { + return a * b; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..131e0da --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,18 @@ +spring: + application: + name: ${artifactId} + version: ${version} + jmx.enabled: true +management: + endpoint: + health: + probes.enabled: true + group: + readiness.include: "*" + liveness.include: livenessState,diskSpace,ping + endpoints: + jmx: + exposure.include: "*" + web: + exposure.include: info, health +app: diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..b714625 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + + + %msg%n + + + + + true + 20000 + 0 + + + + + true + 20000 + 0 + + + + + + + + + + + + diff --git a/src/test/java/it/gov/pagopa/template/config/SwaggerConfigTest.java b/src/test/java/it/gov/pagopa/template/config/SwaggerConfigTest.java new file mode 100644 index 0000000..af61c01 --- /dev/null +++ b/src/test/java/it/gov/pagopa/template/config/SwaggerConfigTest.java @@ -0,0 +1,25 @@ +package it.gov.pagopa.template.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class SwaggerConfigTest { + + private final Info expectedInfo = new Info() + .title("TITLE") + .description("DESCRPTION") + .version("VERSION"); + + private final SwaggerConfig swaggerConfig = new SwaggerConfig(expectedInfo.getTitle(), expectedInfo.getDescription(), expectedInfo.getVersion()); + + @Test + void whenCustomOpenAPIThenExpectedInfo(){ + // When + OpenAPI result = swaggerConfig.customOpenAPI(); + + // Then + Assertions.assertEquals(expectedInfo, result.getInfo()); + } +} diff --git a/src/test/java/it/gov/pagopa/template/utils/CalculatorTest.java b/src/test/java/it/gov/pagopa/template/utils/CalculatorTest.java new file mode 100644 index 0000000..2bba8fb --- /dev/null +++ b/src/test/java/it/gov/pagopa/template/utils/CalculatorTest.java @@ -0,0 +1,31 @@ +package it.gov.pagopa.template.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; + +class CalculatorTest { + Calculator calculator; + + @BeforeEach + void setUp() { + calculator = new Calculator(); + } + + @Test + @DisplayName("Simple multiplication should work") + void testMultiply() { + assertEquals(20, calculator.multiply(4, 5), + "Regular multiplication should work"); + } + + @RepeatedTest(5) + @DisplayName("Ensure correct handling of zero") + void testMultiplyWithZero() { + assertEquals(0, calculator.multiply(0, 5), "Multiple with zero should be zero"); + assertEquals(0, calculator.multiply(5, 0), "Multiple with zero should be zero"); + } +}