Compare commits

...

166 Commits

Author SHA1 Message Date
Tom Hombergs
c347841a3c replaced integers with more meaningful Strings 2021-10-04 07:56:33 +11:00
Tom Hombergs
d715f36b80 feature flag patterns 2021-10-03 09:39:55 +11:00
Tom Hombergs
cf86383325 feature flag patterns 2021-09-26 09:06:46 +10:00
Tom Hombergs
a09f5e7aa0 Merge pull request #113 from pratikdas/spring-scheduler
Spring-scheduler
2021-09-19 07:31:30 +10:00
Pratik Das
b72173ad27 updated build-all 2021-09-18 10:49:34 +05:30
Pratik Das
aac07331e8 added code for scheduler 2021-09-18 10:37:07 +05:30
Tom Hombergs
45590a81c2 Merge pull request #112 from pratikdas/spring-cloudwatch
added code for Spring boot CloudWatch
2021-09-07 07:21:54 +10:00
Pratik Das
1e39c49cca Merge branch 'spring-cloudwatch' of https://github.com/pratikdas/code-examples into spring-cloudwatch 2021-09-03 23:27:09 +05:30
Pratik Das
c8bd0660f5 added raedme 2021-09-03 23:26:39 +05:30
Pratik Das
2874daed69 Merge branch 'master' into spring-cloudwatch 2021-09-03 23:22:07 +05:30
Pratik Das
35a820acbb added code 2021-09-03 23:14:32 +05:30
Tom Hombergs
83e5e1048e Merge pull request #111 from saajn/spring-resilience
Code for Spring Boot Resilience4j RateLimiter article
2021-09-02 07:29:05 +10:00
Saajan
28147ad44c Merge branch 'master' of https://github.com/thombergs/code-examples into spring-resilience 2021-08-30 16:44:27 +05:30
Saajan
154453808d Initial commit of code for Spring Boot Resilience4j RateLimiter article 2021-08-30 16:43:47 +05:30
Tom Hombergs
b7139e9073 Merge pull request #105 from thombergs/structured-logging
code example for structured logging
2021-08-16 20:33:40 +10:00
Tom Hombergs
64499f5582 code example for structured logging 2021-08-16 20:22:15 +10:00
Tom Hombergs
c870f5dd4c Merge pull request #104 from thombergs/zero-downtime
Zero downtime
2021-08-04 06:53:08 +10:00
Tom Hombergs
946a9de553 Merge branch 'master' into zero-downtime
# Conflicts:
#	build-all.sh
2021-08-04 06:41:55 +10:00
Tom Hombergs
bf004661b0 zero-downtime example 2021-08-04 06:35:11 +10:00
Tom Hombergs
9cb21ffb8a zero-downtime example 2021-08-04 06:33:55 +10:00
Tom Hombergs
375928575c zero-downtime example 2021-08-04 06:33:14 +10:00
Tom Hombergs
36a5bdda1e Merge pull request #102 from pratikdas/csrf
CSRF Code examples
2021-07-31 12:37:06 +10:00
Tom Hombergs
d0b88ace16 .properties -> .yml 2021-07-24 06:51:49 +10:00
Tom Hombergs
2e74db8684 wip 2021-07-24 06:44:33 +10:00
Tom Hombergs
597844a483 Merge pull request #101 from saajn/spring-resilience
Examples for the Spring Boot Resilience4j article
2021-07-24 06:22:35 +10:00
Pratik Das
3c934fd394 added code 2021-07-22 15:14:26 +05:30
Saajan
c66d4bd7d7 Added readme file and updated build-all 2021-07-21 15:41:06 +05:30
Saajan
5deb6fb09c Initial commit 2021-07-21 15:34:24 +05:30
Tom Hombergs
55dfc429cb remove snapshot dependency 2021-07-18 10:56:49 +10:00
Tom Hombergs
5a9e2751db Merge remote-tracking branch 'origin/master' 2021-07-18 10:47:30 +10:00
Tom Hombergs
9e6f17cf1e remove snapshot dependency 2021-07-18 10:47:25 +10:00
Tom Hombergs
e7c95ea2e4 Merge pull request #99 from pratikdas/cors
cors
2021-07-18 10:19:09 +10:00
Tom Hombergs
16815980a9 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	spring-boot/feature-flags/.mvn/wrapper/MavenWrapperDownloader.java
#	spring-boot/feature-flags/.mvn/wrapper/maven-wrapper.properties
2021-07-17 07:06:33 +10:00
Tom Hombergs
d49982299d add Maven Wrapper 2021-07-17 07:06:23 +10:00
Tom Hombergs
cf7ecd5f4f zero-downtime example 2021-07-17 07:05:13 +10:00
Pratik Das
406a57d203 added code 2021-07-05 18:09:25 +05:30
Tom Hombergs
a0c58111ec Merge pull request #98 from thombergs/feature-flags
feature flags
2021-07-04 10:03:32 +10:00
Tom Hombergs
355ceb2517 Merge remote-tracking branch 'origin/feature-flags' into feature-flags 2021-07-04 09:58:49 +10:00
Tom Hombergs
c4d2d74f70 add maven wrapper 2021-07-04 09:58:43 +10:00
Tom Hombergs
058bf95ed6 Merge branch 'master' into feature-flags 2021-07-04 09:53:29 +10:00
Tom Hombergs
adf0b55548 feature flags 2021-07-03 07:56:18 +10:00
Tom Hombergs
7b76a8a562 Merge pull request #97 from pratikdas/spring-ses
Spring cloud ses
2021-06-27 08:11:56 +10:00
Tom Hombergs
f6501aff2c Merge branch 'master' into spring-ses 2021-06-27 08:09:48 +10:00
Tom Hombergs
0101fc58f0 Merge pull request #93 from murtuza-ranapur/spring-redis-cache
Caching with Spring Cloud AWS and ElastiCache
2021-06-27 07:25:30 +10:00
Tom Hombergs
36e25d3b7e Merge branch 'master' into spring-redis-cache 2021-06-27 07:16:21 +10:00
Pratik Das
c36ad11f17 build-all updated 2021-06-23 15:16:55 +04:00
Pratik Das
a19a7faf43 added ses code 2021-06-23 15:11:49 +04:00
Tom Hombergs
9f8e3f3bd5 Merge pull request #96 from pratikdas/spring-camel
Spring camel
2021-06-21 08:57:13 +10:00
Murtuza Ranapur
6784d3c31f Merge branch 'master' into spring-redis-cache 2021-06-20 21:22:58 +05:30
Murtuza Ranapurwala
8a253fe371 Move project to aws folder 2021-06-20 14:28:21 +05:30
Pratik Das
03517e3037 updated Readme 2021-06-13 16:47:57 +04:00
Pratik Das
ec71062edb added Readme 2021-06-13 16:44:42 +04:00
Pratik Das
8d82de705f added code 2021-06-13 15:10:52 +04:00
Tom Hombergs
84f58908ed Merge pull request #94 from pratikdas/aws-spring-dynamodb
Dynamodb with spring code examples
2021-06-13 07:25:21 +10:00
Murtuza Ranapurwala
6042b5c086 Adds Spring Cloud AWS for caching 2021-06-06 21:31:26 +05:30
Tom Hombergs
0eaea89433 logging examples 2021-06-06 19:54:33 +10:00
Pratik Das
cca9825c1c updates repository and test 2021-06-05 15:30:12 +04:00
Pratik Das
59747d5a60 edited readme 2021-06-05 15:26:52 +04:00
Pratik Das
3883b0b337 edited readme 2021-06-05 15:18:38 +04:00
Pratik Das
c41d61f17a added readme 2021-06-05 15:15:28 +04:00
Tom Hombergs
f9bde4b1e2 add maven wrapper 2021-06-05 15:55:01 +10:00
Tom Hombergs
5168068e7f logback example 2021-06-05 15:53:18 +10:00
Tom Hombergs
d2acf47492 add logback example project 2021-06-05 15:51:16 +10:00
Tom Hombergs
b8358fc949 add maven wrapper 2021-06-05 07:54:53 +10:00
Tom Hombergs
52c92afd68 add maven wrapper 2021-06-05 07:53:51 +10:00
Tom Hombergs
bb4cd9e297 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	build-all.sh
2021-06-05 07:51:33 +10:00
Tom Hombergs
0a37bcbba9 add log4j example 2021-06-05 07:50:27 +10:00
Pratik Das
64c61b74e4 Merge branch 'aws-spring-dynamodb' of https://github.com/pratikdas/code-examples into aws-spring-dynamodb 2021-06-03 13:12:57 +04:00
Pratik Das
58cd6f04dd renamed folder 2021-06-03 13:08:57 +04:00
Pratik Das
78610bb86b Merge branch 'master' into aws-spring-dynamodb 2021-06-03 12:32:49 +04:00
Pratik Das
cb69d89ca5 updated build-all 2021-06-03 12:29:44 +04:00
Pratik Das
88f18e1d3d added code for 2 modules 2021-06-03 12:24:06 +04:00
Murtuza Ranapurwala
10e8867d4b Adds README.md 2021-05-31 20:35:41 +05:30
Murtuza Ranapurwala
d66a38943f Initial commit 2021-05-31 20:28:31 +05:30
Tom Hombergs
18a6de19d4 Merge pull request #85 from cbenaveen/spring-boot-testconfiguration-code-example
TestConfiguration implementation example
2021-05-28 06:41:18 +10:00
Tom Hombergs
b2c67f3d10 Merge branch 'master' into spring-boot-testconfiguration-code-example 2021-05-28 06:34:55 +10:00
Tom Hombergs
a359a8d3f2 Merge pull request #83 from pratikdas/aws-spring-cloud-rds
added code for spring cloud jdbc for aws rds
2021-05-25 06:45:04 +10:00
Pratik Das
273111b0c6 removed jdbc 2021-05-21 15:51:24 +04:00
K. Naveen Kumar
46fe09352e Removed all Sysout's instead using SLF4J logger 2021-05-20 21:21:04 +05:30
K. Naveen Kumar
ab79a6be97 Changes: 1) Remove spring boot actuator dependency 2) Renamed a static inner class to WebClientTestConfiguration in UsingStaticInnerTestConfiguration.jav 3) Added spring-boot/spring-boot-testconfiguration to build-all.sh file 2021-05-20 21:10:36 +05:30
K. Naveen Kumar
c4a70d8865 TestConfiguration implementation example that is accompanies Test Configuration blog post 2021-05-15 13:24:23 +05:30
Pratik Das
bcd975e02f added rds code 2021-05-13 16:08:18 +04:00
Pratik Das
7fd1bdc29f added rds code 2021-05-13 15:56:32 +04:00
Tom Hombergs
4b69588894 Merge pull request #82 from pratikdas/aws-spring-cloud-messaging
code example aws-spring-cloud-messaging
2021-05-10 07:04:59 +10:00
Tom Hombergs
88936def38 Merge pull request #79 from pratikdas/patch-1
Update README.md to correct spelling of AWS
2021-05-08 07:24:59 +10:00
Tom Hombergs
caf96dd931 Merge pull request #78 from pratikdas/thread-dump-analyzer
Thread dump analyzer
2021-05-05 06:56:13 +10:00
Pratik Das
6b13e04aea Merge branch 'master' into aws-spring-cloud-messaging 2021-05-01 20:47:18 +04:00
Pratik Das
a9795c3298 modified build-all 2021-05-01 20:31:49 +04:00
Pratik Das
ecb2634a77 modified pom 2021-05-01 18:48:25 +04:00
Pratik Das
237a5a6441 added readme 2021-05-01 18:30:59 +04:00
Pratik Das
674d4feb17 code example aws-spring-cloud-messaging 2021-05-01 18:26:28 +04:00
Tom Hombergs
610906f891 Merge pull request #80 from saajn/actuator2
Examples for the Actuator article
2021-04-30 07:16:36 +10:00
Tom Hombergs
258f7fb137 Merge branch 'master' into actuator2 2021-04-30 07:05:14 +10:00
Tom Hombergs
475ef47881 Merge pull request #77 from silenum/feature/clean-unit-tests-with-mockito
added code examples for clean unit tests with mockito
2021-04-25 10:35:17 +10:00
Saajan
39c28dddf0 Moved the counter 2021-04-24 20:24:25 +05:30
Saajan
2b8664e49e Added module to build-all 2021-04-24 19:56:12 +05:30
Saajan
d0c5706133 Initial check in 2021-04-24 19:43:07 +05:30
Pratik Das
cd45701470 Update README.md
Important::Correction in the spelling of AWS.
2021-04-22 05:24:29 +04:00
Tom Hombergs
070ad968e6 Merge pull request #76 from pratikdas/aws-terraform
updated git ignore to exclude DS-Store and uploaded Terraform aws source
2021-04-21 07:57:15 +10:00
Pratik Das
c29993b416 thread dump example 2021-04-20 18:17:31 +04:00
Pratik Das
a481a48992 thread dump example 2021-04-20 18:05:48 +04:00
Pratik Das
3be0e85a77 thread dumo example 2021-04-20 17:46:27 +04:00
Lukas Leuenberger
973cb7a081 updated build-all.sh for mockito 2021-04-20 00:02:06 +02:00
Lukas Leuenberger
33dacb36de added code examples for clean unit tests with mockito 2021-04-20 00:02:06 +02:00
Pratik Das
aaa07d4c70 added README 2021-04-16 16:24:21 +04:00
Pratik Das
23cd4e4da7 updated git ignore 2021-04-16 16:01:29 +04:00
Tom Hombergs
43b9c83917 Merge pull request #74 from bakic/service-provider-interface
Service Provider Interface
2021-03-26 07:33:29 +11:00
Abdelbaki BEN ELHAJ SLIMENE
b558643405 review changes 2021-03-24 16:21:16 +01:00
Tom Hombergs
acdf98e71b Merge pull request #67 from thombergs/dependabot/npm_and_yarn/pact/pact-react-consumer/axios-0.21.1
Bump axios from 0.18.0 to 0.21.1 in /pact/pact-react-consumer
2021-03-24 06:36:33 +11:00
Abdelbaki BEN ELHAJ SLIMENE
c30b27b240 Service Provider Interface 2021-03-14 21:26:24 +01:00
Tom Hombergs
dbdd4d20cb update testcontainers to hopefully fix CI issue 2021-02-18 06:37:43 +11:00
Tom Hombergs
379f0d6799 Merge pull request #71 from pratikdas/heapdump
heap dump
2021-02-18 06:28:58 +11:00
Pratik Das
8e299cf819 Merge branch 'heapdump' of https://github.com/pratikdas/code-examples into heapdump 2021-02-17 15:12:13 +05:30
Pratik Das
14536868c5 removed target 2021-02-17 15:11:11 +05:30
Pratik Das
bbca460ed2 Merge branch 'master' into heapdump 2021-02-17 15:05:07 +05:30
Pratik Das
cd2e56a959 removed target 2021-02-17 14:59:12 +05:30
Pratik Das
143a5b53d0 removed target 2021-02-17 13:54:36 +05:30
Pratik Das
fec088b7db folder moved 2021-02-17 13:52:29 +05:30
Pratik Das
d68609b407 code added 2021-02-12 23:56:24 +05:30
Pratik Das
8b1ae3f3ed code added 2021-02-12 20:50:02 +05:30
Tom Hombergs
0ccd671636 fixing testcontainers issue 2021-02-09 07:31:03 +11:00
Tom Hombergs
1dac3ad175 trying to fix "Docker image not found" error 2021-02-09 07:18:04 +11:00
Tom Hombergs
91e38a8361 trying to fix "Docker image not found" error 2021-02-09 07:07:43 +11:00
Tom Hombergs
071651aa67 Merge pull request #70 from jgoerner/aws/s3
Aws/s3
2021-02-09 06:33:33 +11:00
jgoerner
a6c5ab4cd7 Add module to build script 2021-02-07 00:49:30 +01:00
jgoerner
c28b2d4a73 Update Readme 2021-02-03 21:13:32 +01:00
jgoerner
8bca7692e2 Finish initial version 2021-02-03 21:11:58 +01:00
Tom Hombergs
d1b5b19dd6 Merge pull request #69 from ortelagode/cookies
Cookies
2021-01-26 08:21:43 +11:00
Tom Hombergs
d5ff8dae58 add cookie module to CI build 2021-01-26 08:10:22 +11:00
Ortela Gode
4bb5bb8af9 updated name of cookie 2021-01-23 22:52:00 +01:00
Ortela Gode
e03595d5bd updated readme 2021-01-18 22:08:50 +01:00
Tom Hombergs
b5cfe4ad2a Update README.md 2021-01-18 08:19:21 +11:00
Tom Hombergs
4a64888977 Merge pull request #68 from pratikdas/graphql
graphql code
2021-01-18 08:08:21 +11:00
Ortela Gode
44d082c163 added example project for cookies 2021-01-17 20:01:49 +01:00
Pratik Das
8b3fc4cf5c graphql code 2021-01-15 18:18:14 +05:30
Pratik Das
5bd780f9c8 Merge branch 'master' into graphql 2021-01-15 18:06:39 +05:30
Pratik Das
eba824b908 graphql code 2021-01-15 17:55:28 +05:30
dependabot[bot]
8de5f6d41c Bump axios from 0.18.0 to 0.21.1 in /pact/pact-react-consumer
Bumps [axios](https://github.com/axios/axios) from 0.18.0 to 0.21.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.18.0...v0.21.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-05 03:23:41 +00:00
Tom Hombergs
381be70991 Merge pull request #66 from murtuza-ranapur/spring-boot-exception-handling
Adds Code example for Spring Boot exception Handling tutorial
2020-12-30 07:48:53 +11:00
murtuza-ranapur
87a6cac6b3 Adds logs 2020-12-28 23:13:28 +05:30
murtuza-ranapur
fef6c48fe1 Uses proper build command 2020-12-27 16:03:47 +05:30
murtuza-ranapur
3f59bd2ea4 Adds Code example for Spring Boot exception Handling tutorial 2020-12-27 15:54:15 +05:30
Tom Hombergs
e2d6cc3acb Merge pull request #64 from saajn/circuitbreaker
Circuitbreaker
2020-12-21 06:49:54 +11:00
Tom Hombergs
fef53fa72a fix link 2020-12-18 06:38:48 +11:00
Tom Hombergs
6b2ad23059 Merge pull request #63 from pratikdas/spring-boot-elasticsearch
Spring boot elasticsearch
2020-12-18 06:38:10 +11:00
Pratik Das
368a059cfe updated read.me 2020-12-17 19:58:06 +05:30
Pratik Das
67b4cbe08f modified 2020-12-17 14:23:15 +05:30
Saajan
0514f9f517 Merge branch 'master' of https://github.com/thombergs/code-examples into circuitbreaker 2020-12-14 12:30:10 +05:30
Saajan
76bcc6c016 Added circuitbreaker module to build-all.sh 2020-12-14 12:11:28 +05:30
Saajan
fd14c4c13e Added readme file 2020-12-14 12:07:34 +05:30
Saajan
39dbdc129f Added maven wrapper 2020-12-14 12:03:57 +05:30
Saajan
dbaa38a5fe Initial check in 2020-12-14 12:00:48 +05:30
Pratik Das
8f6e222ef2 Merge branch 'spring-boot-elasticsearch' of https://github.com/pratikdas/code-examples into spring-boot-elasticsearch 2020-12-13 18:04:37 +05:30
Pratik Das
88c5ff3dc7 added es code 2020-12-13 17:58:28 +05:30
Pratik Das
4cf2cbc10c Merge branch 'master' into spring-boot-elasticsearch 2020-12-13 17:40:20 +05:30
Pratik Das
e4e3d8a55b added es code 2020-12-13 17:32:44 +05:30
Pratik Das
d41fa4a006 added es code 2020-12-13 17:13:58 +05:30
Pratik Das
48cdce6e65 added es code 2020-12-13 17:12:14 +05:30
Tom Hombergs
2d8c099993 add gradle wrapper 2020-12-08 06:21:39 +11:00
Tom Hombergs
1c92604f3b add module about mocking Spring Boot modules 2020-12-08 06:18:06 +11:00
Tom Hombergs
6a63059b8c Merge pull request #62 from murtuza-ranapur/specification
Adds Intro to specification example code
2020-11-16 11:24:39 +11:00
murtuza-ranapur
1de287bcdb Adds review changes 2020-11-14 18:40:42 +05:30
murtuza-ranapur
f044fcc228 Adds review changes 2020-11-14 16:55:38 +05:30
murtuza-ranapur
ee1712c245 Fix failing test cases 2020-11-09 23:22:09 +05:30
murtuza-ranapur
8f03b89816 Adds specification to build all file 2020-11-09 23:17:34 +05:30
murtuza-ranapur
f751203867 * Adds Intro to specification example code 2020-11-09 23:15:03 +05:30
811 changed files with 41639 additions and 341 deletions

View File

@@ -17,7 +17,7 @@ jobs:
matrix:
# The MODULE environment variable is evaluated in build-all.sh to run a subset
# of the builds. This way, multiple modules can be built in parallel.
module: [ "module1", "module2", "module3", "module4", "module5" ]
module: [ "module1", "module2", "module3", "module4", "module5", "module6" ]
steps:

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
**/.idea/
**/*.iml
**/.DS_Store
**/.terraform

View File

@@ -0,0 +1,11 @@
# Terraform for creating AWS Resources
Example code to create/update AWS resources with Terraform.
Examples include Terraform capabilities of using modules, input variables and using Terraform cloud.
## Blog posts
Blog posts about this topic:
* [Using Terraform to create AWS resources](https://reflectoring.io/terraform-aws/)

View File

@@ -0,0 +1,41 @@
terraform {
backend "remote" {
hostname = "app.terraform.io"
organization = "pratikorg"
token = "pj7p59JFwSC4jQ.atlasv1.qfmTxLjTfaM5zKyaQrcGzuTojv6oCyLIoIAO7DkA2ieQY7OyINjINGGMiTczt62p1bs"
workspaces {
name = "my-tf-workspace"
}
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.36"
}
}
}
provider "aws" {
profile = "default"
region = "us-west-2"
}
module "app_server" {
source = "./modules/application"
ec2_instance_type = "t2.micro"
ami = "ami-830c94e3"
tags = {
Name = "server for web"
Env = "dev"
}
}
module "app_storage" {
source = "./modules/storage/"
bucket_name = "io.pratik.tf-example-bucket"
env = "dev"
}

View File

@@ -0,0 +1,5 @@
resource "aws_instance" "vm-web" {
ami = var.ami
instance_type = var.ec2_instance_type
tags = var.tags
}

View File

@@ -0,0 +1,4 @@
output "instanceID" {
description = "ID of ec2 instance"
value = aws_instance.vm-web.id
}

View File

@@ -0,0 +1,16 @@
variable "ec2_instance_type" {
description = "Instance type"
type = string
}
variable "ami" {
description = "ami id"
type = string
}
variable "tags" {
description = "Tags to set on the bucket."
type = map(string)
default = {Name = "server for web"
Env = "dev"}
}

View File

@@ -0,0 +1,9 @@
resource "aws_s3_bucket" "s3_bucket" {
bucket = format("%s-%s",var.bucket_name,var.env)
acl = "private"
tags = {
Name = var.bucket_name
Environment = var.env
}
}

View File

@@ -0,0 +1,4 @@
output "arn" {
description = "ARN of the bucket"
value = aws_s3_bucket.s3_bucket.arn
}

View File

@@ -0,0 +1,13 @@
# Input variable definitions
variable "bucket_name" {
description = "Name of bucket"
type = string
}
variable "env" {
description = "Environment like dev, prod"
type = string
}

View File

@@ -0,0 +1,24 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.27"
}
}
}
provider "aws" {
profile = "default"
region = "us-west-2"
}
resource "aws_instance" "vm-web" {
ami = "ami-830c94e3"
instance_type = var.ec2_instance_type
tags = {
Name = "server for web"
Env = "dev"
}
}

View File

@@ -0,0 +1 @@
ec2_instance_type = "t2.micro"

View File

@@ -0,0 +1,4 @@
variable "ec2_instance_type" {
description = "AWS EC2 instance type."
type = string
}

View File

@@ -0,0 +1,33 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.36"
}
}
}
provider "aws" {
profile = "default"
region = "us-west-2"
}
module "app_server" {
source = "./modules/application"
ec2_instance_type = "t2.micro"
ami = "ami-830c94e3"
tags = {
Name = "server for web"
Env = "dev"
}
}
module "app_storage" {
source = "./modules/storage/"
bucket_name = "io.pratik.tf-example-bucket"
env = "dev"
}

View File

@@ -0,0 +1,5 @@
resource "aws_instance" "vm-web" {
ami = var.ami
instance_type = var.ec2_instance_type
tags = var.tags
}

View File

@@ -0,0 +1,4 @@
output "instanceID" {
description = "ID of ec2 instance"
value = aws_instance.vm-web.id
}

View File

@@ -0,0 +1,16 @@
variable "ec2_instance_type" {
description = "Instance type"
type = string
}
variable "ami" {
description = "ami id"
type = string
}
variable "tags" {
description = "Tags to set on the bucket."
type = map(string)
default = {Name = "server for web"
Env = "dev"}
}

View File

@@ -0,0 +1,9 @@
resource "aws_s3_bucket" "s3_bucket" {
bucket = format("%s-%s",var.bucket_name,var.env)
acl = "private"
tags = {
Name = var.bucket_name
Environment = var.env
}
}

View File

@@ -0,0 +1,4 @@
output "arn" {
description = "ARN of the bucket"
value = aws_s3_bucket.s3_bucket.arn
}

View File

@@ -0,0 +1,13 @@
# Input variable definitions
variable "bucket_name" {
description = "Name of bucket"
type = string
}
variable "env" {
description = "Environment like dev, prod"
type = string
}

View File

@@ -0,0 +1,24 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.27"
}
}
}
provider "aws" {
profile = "default"
region = "us-west-2"
}
resource "aws_instance" "vm-web" {
ami = "ami-830c94e3"
instance_type = "t2.micro"
tags = {
Name = "server for web"
Env = "dev"
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2007-present the original author or 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
*
* http://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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

BIN
aws/cdk/.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

0
aws/cdk/mvnw vendored Normal file
View File

182
aws/cdk/mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,182 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

View File

@@ -0,0 +1,23 @@
package com.myorg;
import software.amazon.awscdk.core.Construct;
import software.amazon.awscdk.core.Stack;
import software.amazon.awscdk.core.StackProps;
import software.amazon.awscdk.services.s3.Bucket;
public class SecondStack extends Stack {
public SecondStack(final Construct scope, final String id) {
this(scope, id, null);
}
public SecondStack(final Construct scope, final String id, final StackProps props) {
super(scope, id, props);
String bucketName = (String) this.getNode().tryGetContext("bucketName2");
Bucket bucket = Bucket.Builder.create(this, "bucket")
.bucketName(bucketName)
.build();
}
}

141
aws/s3/.gitignore vendored Normal file
View File

@@ -0,0 +1,141 @@
# Created by https://www.toptal.com/developers/gitignore/api/java,intellij+all,gradle
# Edit at https://www.toptal.com/developers/gitignore?templates=java,intellij+all,gradle
### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
# out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Gradle ###
.gradle
build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
### Gradle Patch ###
**/build/
# End of https://www.toptal.com/developers/gitignore/api/java,intellij+all,gradle
# Dumps of the meta-db
data/*.db

167
aws/s3/Readme.md Normal file
View File

@@ -0,0 +1,167 @@
<!-- PROJECT LOGO -->
<br />
<p align="center">
<img src="images/logo.png" alt="Logo" width="200" height="200">
<h3 align="center">Private File Upload</h3>
<p align="center">
Build on Spring Cloud & AWS S3
</p>
</p>
<!-- TABLE OF CONTENTS -->
## Table of Contents
* [About the Project](#about-the-project)
* [Built With](#built-with)
* [Getting Started](#getting-started)
* [Prerequisites](#prerequisites)
* [Installation](#installation)
* [Usage](#usage)
* [Contact](#contact)
* [Acknowledgements](#acknowledgements)
<!-- ABOUT THE PROJECT -->
## About The Project
<p align="center">
<img src="images/usage.gif">
</p>
### Built With
- Spring Boot
- Spring Cloud
- AWS S3
- ☕️ & ❤️
<!-- GETTING STARTED -->
## Getting Started
### Prerequisites
In order to run this application you should have an Amazon Web Services (AWS) account.
### Installation
1. Update the `application.yaml` settings value for `cloud.aws.credentials.profile-name` to the name of your local AWS-profile
2. run `./gradlew bootRun`
3. Open your browser at `localhost:8080`
<!-- USAGE EXAMPLES -->
## Usage
The application wraps a lot of S3-related API calls in easy to use UI elements to
- create/delete buckets (mapped as "spaces" so we can give them non-unique names)
- upload/delete files
- make files public/private
- create pre-signed URLs for files
- create bucket policy rules to set the lifecycle duration for elements
More detailed instructions can be found here:
<details>
<summary>🪣 Create a bucket</summary>
1. Navigate to the _Spaces_ section
2. Click on _New Space_
3. Enter the name and click _Submit_
4. A message should pop up to indicate success
</details>
<details>
<summary>🗂 Upload a File</summary>
1. Navigate to the _Spaces_ section
2. Select _Details_ on the target Space/Bucket
3. Click on _Upload File_
4. Pick our file, provide a name and click _Submit_
5. A message should pop up to indicate success
</details>
<details>
<summary>🔎 List all Objects in a Bucket</summary>
1. Navigate to the _Spaces_ section
2. Select _Details_ on the target Space/Bucket
3. You see a list of all objects stored in the bucket
</details>
<details>
<summary>🌐 Get an Object's URL</summary>
1. Navigate to the _Spaces_ section
2. Select _Details_ on the target Space/Bucket
3. Select _Download_ on the target object
4. The object's URL shall be opened in a new tab
</details>
<details>
<summary>📢 Make an object public</summary>
1. Navigate to the _Spaces_ section
2. Select _Details_ on the target Space/Bucket
3. Select _Make Public_ on the target object
4. A message should pop up to indicate success
</details>
<details>
<summary>🤫 Make an Object private</summary>
1. Navigate to the _Spaces_ section
2. Select _Details_ on the target Space/Bucket
3. Select _Make Private_ on the target object
4. A message should pop up to indicate success
</details>
<details>
<summary>🔥 Delete an Object</summary>
1. Navigate to the _Spaces_ section
2. Select _Details_ on the target Space/Bucket
3. Select _Delete_ on the target object
4. The list of objects should reload without the deleted one
</details>
<details>
<summary>☄️ Delete a Bucket</summary>
1. Navigate to the _Spaces_ section
2. Select _Delete_ on the target Space/Bucket
3. The list of buckets should reload without the deleted one
</details>
<details>
<summary>👾 Generate a pre-signed URL</summary>
1. Navigate to the _Spaces_ section
2. Select _Details_ on the target Space/Bucket
3. Select _Magic Link_ on the target object
4. A message should pop up, containing a pre-signed URL for that object (which is valid for 15 minutes)
</details>
<details>
<summary>⏳ Set Expiration on Bucket</summary>
1. Navigate to the _Spaces_ section
2. Select _Make Temporary_ on the target Space/Bucket
3. Select _Delete_ on the target object
4. A message should pop up to indicate success
</details>
<!-- CONTACT -->
## Contact
Joshua Görner - [jgoerner](https://www.linkedin.com/in/jgoerner/) - joshua.goerner[at]gmail.com
<!-- ACKNOWLEDGEMENTS -->
## Acknowledgements
* [O. Drew](https://github.com/othneildrew/Best-README-Template) - nice GH Readme template
* [Hatchful](https://hatchful.shopify.com/) - Easy Logo Generation

41
aws/s3/build.gradle Normal file
View File

@@ -0,0 +1,41 @@
plugins {
id 'org.springframework.boot' version '2.4.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'io.jgoerner'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
ext {
// set('springCloudVersion', "Hoxton.SR9")
set('springCloudVersion', "Finchley.SR1")
}
dependencies {
annotationProcessor 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.cloud:spring-cloud-starter-aws'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.projectlombok:lombok:1.18.16'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
test {
useJUnitPlatform()
}

0
aws/s3/data/.gitkeep Normal file
View File

BIN
aws/s3/gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Mon Jan 04 22:38:53 CET 2021
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

185
aws/s3/gradlew vendored Executable file
View File

@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or 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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# 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"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# 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
;;
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"
which java >/dev/null 2>&1 || 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
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
aws/s3/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,89 @@
@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
@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=.
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%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
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%"=="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!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

BIN
aws/s3/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
aws/s3/images/usage.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

1
aws/s3/settings.gradle Normal file
View File

@@ -0,0 +1 @@
rootProject.name = 's3'

View File

@@ -0,0 +1,38 @@
package io.jgoerner.s3;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties;
import org.springframework.cloud.aws.autoconfigure.context.ContextInstanceDataAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.thymeleaf.templateresolver.FileTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
@SpringBootApplication(exclude = ContextInstanceDataAutoConfiguration.class)
public class S3Application {
private final ThymeleafProperties properties;
@Value("${spring.thymeleaf.templates_root:}")
private String templatesRoot;
public S3Application(ThymeleafProperties properties) {
this.properties = properties;
}
public static void main(String[] args) {
SpringApplication.run(S3Application.class, args);
}
@Bean
public ITemplateResolver defaultTemplateResolver() {
FileTemplateResolver resolver = new FileTemplateResolver();
resolver.setSuffix(properties.getSuffix());
resolver.setPrefix(templatesRoot);
resolver.setTemplateMode(properties.getMode());
resolver.setCacheable(properties.isCache());
return resolver;
}
}

View File

@@ -0,0 +1,128 @@
package io.jgoerner.s3.adapter.in;
import io.jgoerner.s3.application.port.in.object.*;
import io.jgoerner.s3.application.port.in.space.*;
import io.jgoerner.s3.domain.Object;
import io.jgoerner.s3.domain.ObjectPartial;
import io.jgoerner.s3.domain.Space;
import io.jgoerner.s3.domain.SpacePartial;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.net.URL;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("api")
@Log4j2
public class RestApi {
private final CreateSpace spaceCreator;
private final GetAllSpaces allSpaceGetter;
private final RemoveSpace spaceRemover;
private final GetAllObjects allObjectsInSpaceGetter;
private final UploadObject objectUploader;
private final UpdateObject objectUpdater;
private final RemoveObject objectDeleter;
private final ForceRemoveSpace forceSpaceRemover;
private final CreateLink linkCreator;
private final SetTTL ttlUpdater;
private final RemoveTTL ttlRemover;
public RestApi(
CreateSpace spaceCreator,
GetAllSpaces allSpaceGetter,
RemoveSpace spaceRemover,
GetAllObjects allObjectsInSpaceGetter,
UploadObject objectUploader,
UpdateObject objectUpdater,
RemoveObject objectDeleter,
ForceRemoveSpace forceSpaceRemover,
CreateLink linkCreator,
SetTTL ttlUpdater,
RemoveTTL ttlRemover) {
this.spaceCreator = spaceCreator;
this.allSpaceGetter = allSpaceGetter;
this.spaceRemover = spaceRemover;
this.allObjectsInSpaceGetter = allObjectsInSpaceGetter;
this.objectUploader = objectUploader;
this.objectUpdater = objectUpdater;
this.objectDeleter = objectDeleter;
this.forceSpaceRemover = forceSpaceRemover;
this.linkCreator = linkCreator;
this.ttlUpdater = ttlUpdater;
this.ttlRemover = ttlRemover;
}
@GetMapping("/space")
List<Space> getSpaces() {
return allSpaceGetter.getAll();
}
@PostMapping("/space/{space}")
Space postSpace(@PathVariable String space) {
return spaceCreator.create(space);
}
@DeleteMapping("/space/{space}")
void deleteSpace(@PathVariable String space, @RequestParam Optional<Boolean> force) {
log.info("Got the value " + force);
force.ifPresentOrElse(
value -> {
if (value) {
forceSpaceRemover.forceRemove(space);
} else {
spaceRemover.remove(space);
}
},
() -> spaceRemover.remove(space));
}
@GetMapping("/space/{space}/object")
List<Object> getObjectsInSpace(@PathVariable String space) {
return allObjectsInSpaceGetter.getAllObjects(space);
}
@SneakyThrows
@PostMapping("/space/{space}/object")
Object postObject(
@PathVariable String space,
@RequestParam("file") MultipartFile file,
@RequestParam(required = false, name = "name") String name) {
var key = name != null ? name : file.getOriginalFilename();
return objectUploader.upload(space, key, file.getInputStream());
}
@PatchMapping("/space/{space}/object/{key}")
Object patchObject(
@PathVariable String space, @PathVariable String key, @RequestBody ObjectPartial body) {
log.info("Got the partial " + body);
return objectUpdater.update(space, key, body);
}
@DeleteMapping("/space/{space}/object/{key}")
void deleteObject(@PathVariable String space, @PathVariable String key) {
objectDeleter.delete(space, key);
}
@PostMapping("/space/{space}/object/{key}/url")
URL createLink(
@PathVariable String space,
@PathVariable String key,
@RequestParam(required = false, name = "duration", defaultValue = "300") Long duration) {
return linkCreator.createLink(space, key, duration);
}
@PatchMapping("/space/{space}")
void patchSpace(@PathVariable String space, @RequestBody(required = false) SpacePartial body) {
log.info("got " + body);
if (body.getTtlInDays() > 1) {
ttlUpdater.setTTL(space, body.getTtlInDays());
} else {
ttlRemover.removeTTL(space);
}
}
}

View File

@@ -0,0 +1,126 @@
package io.jgoerner.s3.adapter.in;
import io.jgoerner.s3.domain.ObjectPartial;
import io.jgoerner.s3.domain.SpacePartial;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.Map;
import java.util.Optional;
@Log4j2
@Controller()
public class View {
private final RestApi api;
public View(RestApi api) {
this.api = api;
}
@GetMapping("space")
String space(Model model) {
var spaces = api.getSpaces();
model.addAttribute("spaces", spaces);
return "space-overview";
}
@GetMapping("space/{name}")
String spaceDetail(@PathVariable String name, Model model) {
model.addAllAttributes(Map.of("space", name, "objects", api.getObjectsInSpace(name)));
return "space-detail";
}
@GetMapping("space/{space}/make-public/{key}")
String makePublic(
@PathVariable String space, @PathVariable String key, RedirectAttributes redirectAttributes) {
api.patchObject(space, key, new ObjectPartial(true));
redirectAttributes.addFlashAttribute("success", "Made object public");
return "redirect:/space/" + space;
}
@GetMapping("space/{space}/make-private/{key}")
String makePrivate(
@PathVariable String space, @PathVariable String key, RedirectAttributes redirectAttributes) {
api.patchObject(space, key, new ObjectPartial(false));
redirectAttributes.addFlashAttribute("success", "Made object private");
return "redirect:/space/" + space;
}
@GetMapping("space/{space}/delete/{key}")
String deleteObject(@PathVariable String space, @PathVariable String key) {
api.deleteObject(space, key);
return "redirect:/space/" + space;
}
@GetMapping("/space/{space}/object-form")
String objectNew(@PathVariable String space, Model model) {
model.addAllAttributes(Map.of("space", space, "object", new ObjectForm()));
return "object-form";
}
@PostMapping("/space/{space}/object-form")
String postObject(
@PathVariable String space,
@RequestParam("file") MultipartFile file,
@ModelAttribute ObjectForm form,
RedirectAttributes redirectAttributes) {
api.postObject(space, file, form.getName());
redirectAttributes.addFlashAttribute("success", "Successfully uploaded " + form.getName());
return "redirect:/space/" + space;
}
@GetMapping("/space/{space}/magic/{key}")
String magicLink(
@PathVariable String space, @PathVariable String key, RedirectAttributes redirectAttributes) {
var link = api.createLink(space, key, 15L);
redirectAttributes.addFlashAttribute("warn", link);
return "redirect:/space/" + space;
}
@GetMapping("space-form")
String spaceNew(Model model) {
model.addAttribute("space", new SpaceForm());
return "space-form";
}
@PostMapping("space-form")
String newSpace(@ModelAttribute SpaceForm form, RedirectAttributes redirectAttributes) {
api.postSpace(form.getName());
redirectAttributes.addFlashAttribute("success", "Successfully created " + form.getName());
return "redirect:space";
}
@GetMapping("space/temporary/{name}")
String makeTemporary(@PathVariable String name) {
api.patchSpace(name, new SpacePartial(2));
return "redirect:/space";
}
@GetMapping("space/permanent/{name}")
String makePermanent(@PathVariable String name) {
api.patchSpace(name, new SpacePartial(-1));
return "redirect:/space";
}
@GetMapping("space/delete/{name}")
String delete(@PathVariable String name) {
api.deleteSpace(name, Optional.of(true));
return "redirect:/space";
}
}
@Data
class SpaceForm {
String name;
}
@Data
class ObjectForm {
String name;
}

View File

@@ -0,0 +1,20 @@
package io.jgoerner.s3.adapter.out.h2;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class SpaceEntity {
@Id private String name;
private String bucket;
private Integer ttl;
}

View File

@@ -0,0 +1,69 @@
package io.jgoerner.s3.adapter.out.h2;
import io.jgoerner.s3.application.port.out.space.*;
import io.jgoerner.s3.domain.Space;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
@Component
@Log4j2
public class SpacePersistenceHandler
implements CheckSpaceExistence,
SaveSpace,
RetrieveAllSpaces,
ResolveSpaceName,
DeleteSpace,
RetrieveSpaceByName {
private final SpaceRepository spaceRepository;
public SpacePersistenceHandler(SpaceRepository spaceRepository) {
this.spaceRepository = spaceRepository;
}
@Override
public boolean doesExist(String name) {
return this.spaceRepository.findById(name).isPresent();
}
@Override
public Space save(Space name) {
this.spaceRepository.save(SpacePersistenceHandler.mapPojoToJpa(name));
return name;
}
@Override
public List<Space> findAll() {
return spaceRepository.findAll().stream()
.map(SpacePersistenceHandler::mapJpaToPojo)
.collect(Collectors.toList());
}
private static SpaceEntity mapPojoToJpa(Space space) {
return new SpaceEntity(space.getName(), space.getBucket(), space.getTtl());
}
private static Space mapJpaToPojo(SpaceEntity entity) {
return new Space(entity.getName(), entity.getBucket(), entity.getTtl());
}
@Override
public String resolve(String name) {
var bucket = spaceRepository.findById(name).get().getBucket();
log.info("Space " + name + " was resolved to " + bucket);
return bucket;
}
@Override
public void delete(String name) {
spaceRepository.deleteById(name);
}
@Override
public Space retrieveByName(String name) {
return mapJpaToPojo(spaceRepository.findById(name).orElseThrow());
}
}

View File

@@ -0,0 +1,7 @@
package io.jgoerner.s3.adapter.out.h2;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SpaceRepository extends JpaRepository<SpaceEntity, String> {}

View File

@@ -0,0 +1,143 @@
package io.jgoerner.s3.adapter.out.s3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.services.s3.model.lifecycle.LifecycleFilter;
import com.amazonaws.waiters.WaiterParameters;
import io.jgoerner.s3.application.port.out.bucket.*;
import io.jgoerner.s3.application.port.out.object.*;
import io.jgoerner.s3.domain.Object;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Repository;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Repository
@Log4j2
public class S3Repository
implements CreateBucket,
DeleteBucket,
ListObjects,
SaveObject,
MakeObjectPublic,
MakeObjectPrivate,
DeleteObject,
CreatePresignedUrl,
SetVisibilityInObjectLifecycle,
RemoveVisibilityInObjectLifecycle {
private final AmazonS3Client s3Client;
public S3Repository(AmazonS3Client s3Client) {
this.s3Client = s3Client;
}
@Override
public void create(String bucket) {
// send bucket creation request
s3Client.createBucket(bucket);
log.info("Request to create " + bucket + " sent");
// assure that bucket is available
s3Client.waiters().bucketExists().run(new WaiterParameters<>(new HeadBucketRequest(bucket)));
log.info("Bucket " + bucket + " is ready");
}
@Override
public void delete(String bucket) {
// send deletion request
s3Client.deleteBucket(bucket);
log.info("Request to delete " + bucket + " sent");
// assure bucket is deleted
s3Client.waiters().bucketNotExists().run(new WaiterParameters(new HeadBucketRequest(bucket)));
log.info("Bucket " + bucket + " is deleted");
}
@Override
public List<Object> listObjectsInBucket(String bucket) {
var items =
s3Client.listObjectsV2(bucket).getObjectSummaries().stream()
.parallel()
.map(S3ObjectSummary::getKey)
.map(key -> mapS3ToObject(bucket, key))
.collect(Collectors.toList());
log.info("Found " + items.size() + " objects in the bucket " + bucket);
return items;
}
@Override
public Object safe(String bucket, String key, String name, InputStream payload) {
var metadata = new ObjectMetadata();
metadata.addUserMetadata("name", name);
s3Client.putObject(bucket, key, payload, metadata);
log.info("Sent the request");
return Object.builder().name(name).key(key).url(s3Client.getUrl(bucket, key)).build();
}
@Override
public void makePublic(String bucket, String key) {
s3Client.setObjectAcl(bucket, key, CannedAccessControlList.PublicRead);
log.info("Sent request to make object in bucket " + bucket + " with key " + key + " public");
}
@Override
public void makePrivate(String bucket, String key) {
s3Client.setObjectAcl(bucket, key, CannedAccessControlList.BucketOwnerFullControl);
log.info("Sent request to make object in bucket " + bucket + " with key " + key + " private");
}
@Override
public void delete(String bucket, String key) {
s3Client.deleteObject(bucket, key);
log.info("Sent request to delete file with key " + key + " in bucket " + bucket);
}
@Override
public URL createURL(String bucket, String key, Long duration) {
var date = new Date(new Date().getTime() + duration * 1000); // 1 s * 1000 ms/s
var url = s3Client.generatePresignedUrl(bucket, key, date);
log.info("Generated the signature " + url);
return url;
}
@Override
public void setVisibility(String bucket, Integer ttlInDays) {
s3Client.setBucketLifecycleConfiguration(
bucket,
new BucketLifecycleConfiguration()
.withRules(
new BucketLifecycleConfiguration.Rule()
.withId("custom-expiration-id")
.withFilter(new LifecycleFilter())
.withStatus(BucketLifecycleConfiguration.ENABLED)
.withExpirationInDays(ttlInDays)));
}
@Override
public void removeVisibility(String bucket) {
s3Client.deleteBucketLifecycleConfiguration(bucket);
}
private Object mapS3ToObject(String bucket, String key) {
return Object.builder()
.name(s3Client.getObjectMetadata(bucket, key).getUserMetaDataOf("name"))
.key(key)
.url(s3Client.getUrl(bucket, key))
.isPublic(
s3Client.getObjectAcl(bucket, key).getGrantsAsList().stream()
.anyMatch(grant -> grant.equals(S3Repository.publicObjectReadGrant())))
.build();
}
private static Grant publicObjectReadGrant() {
return new Grant(
GroupGrantee.parseGroupGrantee(GroupGrantee.AllUsers.getIdentifier()), Permission.Read);
}
}

View File

@@ -0,0 +1,7 @@
package io.jgoerner.s3.application.port.in.object;
import java.net.URL;
public interface CreateLink {
URL createLink(String space, String key, Long duration);
}

View File

@@ -0,0 +1,9 @@
package io.jgoerner.s3.application.port.in.object;
import io.jgoerner.s3.domain.Object;
import java.util.List;
public interface GetAllObjects {
List<Object> getAllObjects(String space);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.in.object;
public interface RemoveObject {
void delete(String space, String key);
}

View File

@@ -0,0 +1,8 @@
package io.jgoerner.s3.application.port.in.object;
import io.jgoerner.s3.domain.ObjectPartial;
import io.jgoerner.s3.domain.Object;
public interface UpdateObject {
Object update(String space, String key, ObjectPartial updates);
}

View File

@@ -0,0 +1,9 @@
package io.jgoerner.s3.application.port.in.object;
import io.jgoerner.s3.domain.Object;
import java.io.InputStream;
public interface UploadObject {
Object upload(String space, String name, InputStream payload);
}

View File

@@ -0,0 +1,7 @@
package io.jgoerner.s3.application.port.in.space;
import io.jgoerner.s3.domain.Space;
public interface CreateSpace {
Space create(String name);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.in.space;
public interface ForceRemoveSpace {
void forceRemove(String space);
}

View File

@@ -0,0 +1,9 @@
package io.jgoerner.s3.application.port.in.space;
import io.jgoerner.s3.domain.Space;
import java.util.List;
public interface GetAllSpaces {
List<Space> getAll();
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.in.space;
public interface RemoveSpace {
void remove(String space);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.in.space;
public interface RemoveTTL {
void removeTTL(String space);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.in.space;
public interface SetTTL {
void setTTL(String space, Integer ttlInDays);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.out.bucket;
public interface CreateBucket {
void create(String bucket);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.out.bucket;
public interface DeleteBucket {
void delete(String bucket);
}

View File

@@ -0,0 +1,9 @@
package io.jgoerner.s3.application.port.out.bucket;
import io.jgoerner.s3.domain.Object;
import java.util.List;
public interface ListObjects {
List<Object> listObjectsInBucket(String bucket);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.out.bucket;
public interface RemoveVisibilityInObjectLifecycle {
void removeVisibility(String bucket);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.out.bucket;
public interface SetVisibilityInObjectLifecycle {
void setVisibility(String bucket, Integer ttlInDays);
}

View File

@@ -0,0 +1,7 @@
package io.jgoerner.s3.application.port.out.object;
import java.net.URL;
public interface CreatePresignedUrl {
URL createURL(String bucket, String key, Long duration);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.out.object;
public interface DeleteObject {
void delete(String bucket, String key);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.out.object;
public interface MakeObjectPrivate {
void makePrivate(String bucket, String key);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.out.object;
public interface MakeObjectPublic {
void makePublic(String bucket, String key);
}

View File

@@ -0,0 +1,9 @@
package io.jgoerner.s3.application.port.out.object;
import io.jgoerner.s3.domain.Object;
import java.io.InputStream;
public interface SaveObject {
Object safe(String bucket, String key, String name, InputStream payload);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.out.space;
public interface CheckSpaceExistence {
boolean doesExist(String name);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.out.space;
public interface DeleteSpace {
void delete(String name);
}

View File

@@ -0,0 +1,5 @@
package io.jgoerner.s3.application.port.out.space;
public interface ResolveSpaceName {
String resolve(String name);
}

View File

@@ -0,0 +1,9 @@
package io.jgoerner.s3.application.port.out.space;
import io.jgoerner.s3.domain.Space;
import java.util.List;
public interface RetrieveAllSpaces {
List<Space> findAll();
}

View File

@@ -0,0 +1,7 @@
package io.jgoerner.s3.application.port.out.space;
import io.jgoerner.s3.domain.Space;
public interface RetrieveSpaceByName {
Space retrieveByName(String name);
}

View File

@@ -0,0 +1,7 @@
package io.jgoerner.s3.application.port.out.space;
import io.jgoerner.s3.domain.Space;
public interface SaveSpace {
Space save(Space name);
}

View File

@@ -0,0 +1,105 @@
package io.jgoerner.s3.application.service;
import io.jgoerner.s3.application.port.in.object.CreateLink;
import io.jgoerner.s3.application.port.in.object.RemoveObject;
import io.jgoerner.s3.application.port.in.object.UpdateObject;
import io.jgoerner.s3.application.port.in.object.UploadObject;
import io.jgoerner.s3.application.port.out.object.*;
import io.jgoerner.s3.application.port.out.space.ResolveSpaceName;
import io.jgoerner.s3.domain.Object;
import io.jgoerner.s3.domain.ObjectPartial;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.net.URL;
import java.util.UUID;
@Service
@Log4j2
public class ObjectService implements UploadObject, UpdateObject, RemoveObject, CreateLink {
private final ResolveSpaceName bucketNameResolver;
private final SaveObject objectSaver;
private final MakeObjectPublic objectPublicMaker;
private final MakeObjectPrivate objectPrivateMaker;
private final DeleteObject objectDeleter;
private final CreatePresignedUrl presignedUrlCreator;
public ObjectService(
ResolveSpaceName bucketNameResolver,
SaveObject objectSaver,
MakeObjectPublic objectPublicMaker,
MakeObjectPrivate objectPrivateMaker,
DeleteObject objectDeleter,
CreatePresignedUrl presignedUrlCreator) {
this.bucketNameResolver = bucketNameResolver;
this.objectSaver = objectSaver;
this.objectPublicMaker = objectPublicMaker;
this.objectPrivateMaker = objectPrivateMaker;
this.objectDeleter = objectDeleter;
this.presignedUrlCreator = presignedUrlCreator;
}
@Override
public Object upload(String space, String name, InputStream payload) {
// check if bucket exists
var bucket = bucketNameResolver.resolve(space);
// generate a id & store in lookup table
var key = UUID.randomUUID().toString();
// save
log.info(
"Going to upload the file into to "
+ bucket
+ "/"
+ key
+ " with metadata name of "
+ name);
return objectSaver.safe(bucket, key, name, payload);
}
@Override
public Object update(String space, String key, ObjectPartial updates) {
var bucket = bucketNameResolver.resolve(space);
if (updates.getIsPublic() != null) {
if (updates.getIsPublic()) {
log.info("going to open up to public");
objectPublicMaker.makePublic(bucket, key);
} else {
log.info("going to remove public access");
objectPrivateMaker.makePrivate(bucket, key);
}
}
return null;
}
@Override
public void delete(String space, String key) {
var bucket = bucketNameResolver.resolve(space);
log.info("Going to delete the file with the key " + key + " in the bucket " + bucket);
objectDeleter.delete(bucket, key);
}
@Override
public URL createLink(String space, String key, Long duration) {
var bucket = bucketNameResolver.resolve(space);
log.info(
"Going to generate a link for the file "
+ key
+ " in bucket "
+ bucket
+ " with visibility duration of "
+ duration
+ " seconds");
return presignedUrlCreator.createURL(bucket, key, duration);
}
}

View File

@@ -0,0 +1,155 @@
package io.jgoerner.s3.application.service;
import io.jgoerner.s3.application.port.in.object.GetAllObjects;
import io.jgoerner.s3.application.port.in.object.RemoveObject;
import io.jgoerner.s3.application.port.in.space.*;
import io.jgoerner.s3.application.port.out.bucket.*;
import io.jgoerner.s3.application.port.out.object.DeleteObject;
import io.jgoerner.s3.application.port.out.space.*;
import io.jgoerner.s3.domain.Object;
import io.jgoerner.s3.domain.Space;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
@Service
@Log4j2
public class SpaceService
implements CreateSpace,
GetAllSpaces,
GetAllObjects,
RemoveSpace,
ForceRemoveSpace,
SetTTL,
RemoveTTL {
private final CheckSpaceExistence spaceExistenceChecker;
private final SaveSpace spaceSaver;
private final RetrieveAllSpaces allSpaceRetriever;
private final CreateBucket bucketCreator;
private final ResolveSpaceName bucketNameResolver;
private final DeleteBucket bucketDeleter;
private final DeleteSpace spaceDeleter;
private final ListObjects objectLister;
private final DeleteObject objectDeleter;
private final SetVisibilityInObjectLifecycle objectLifecycleVisibilitySetter;
private final RemoveVisibilityInObjectLifecycle objectLifecycleVisibilityRemover;
private final RetrieveSpaceByName spaceByNameRetriever;
public SpaceService(
CheckSpaceExistence spaceExistenceChecker,
SaveSpace spaceSaver,
RetrieveAllSpaces allSpaceRetriever,
CreateBucket bucketCreator,
ResolveSpaceName bucketNameResolver,
DeleteBucket bucketDeleter,
DeleteSpace spaceDeleter,
ListObjects objectLister,
RemoveObject objectRemover,
DeleteObject objectDeleter,
SetVisibilityInObjectLifecycle objectLifecycleVisibilitySetter,
RemoveVisibilityInObjectLifecycle objectLifecycleVisibilityRemover,
RetrieveSpaceByName spaceByNameRetriever) {
this.spaceExistenceChecker = spaceExistenceChecker;
this.spaceSaver = spaceSaver;
this.allSpaceRetriever = allSpaceRetriever;
this.bucketNameResolver = bucketNameResolver;
this.bucketCreator = bucketCreator;
this.bucketDeleter = bucketDeleter;
this.spaceDeleter = spaceDeleter;
this.objectLister = objectLister;
this.objectDeleter = objectDeleter;
this.objectLifecycleVisibilitySetter = objectLifecycleVisibilitySetter;
this.objectLifecycleVisibilityRemover = objectLifecycleVisibilityRemover;
this.spaceByNameRetriever = spaceByNameRetriever;
}
@Override
public Space create(String name) {
// check if bucket exists
if (spaceExistenceChecker.doesExist(name)) {
log.info("Space " + name + " does already exist");
return null;
}
// create
Space space = new Space(name, "spring-boot-s3-tutorial-" + UUID.randomUUID().toString(), null);
log.info("Mapped space to bucket " + space);
bucketCreator.create(space.getBucket());
// create bucket meta
return this.spaceSaver.save(space);
}
@Override
public List<Space> getAll() {
var buckets = allSpaceRetriever.findAll();
log.info("Found " + buckets.size() + " buckets");
return buckets;
}
@Override
public List<Object> getAllObjects(String space) {
// get bucket from H2
var bucket = bucketNameResolver.resolve(space);
// return all files in bucket
return objectLister.listObjectsInBucket(bucket);
}
@Override
public void remove(String space) {
// get bucket from H2
var bucket = bucketNameResolver.resolve(space);
// delete from S3
bucketDeleter.delete(bucket);
// delete from H2
spaceDeleter.delete(space);
}
@Override
public void forceRemove(String space) {
// get bucket from H2
var bucket = bucketNameResolver.resolve(space);
// empty bucket
getAllObjects(space).stream()
.peek(log::info)
.forEach(object -> objectDeleter.delete(bucket, object.getKey()));
// get rid of bucket
remove(space);
}
@Override
public void setTTL(String space, Integer ttlInDays) {
var bucket = bucketNameResolver.resolve(space);
log.info("Going to adjust the TTL for the bucket " + bucket + " to " + ttlInDays + " day(s)");
// S3
objectLifecycleVisibilitySetter.setVisibility(bucket, ttlInDays);
// H2
var spaceEntity = spaceByNameRetriever.retrieveByName(space);
spaceEntity.setTtl(ttlInDays);
spaceSaver.save(spaceEntity);
}
@Override
public void removeTTL(String space) {
var bucket = bucketNameResolver.resolve(space);
log.info("Going to remove TTL policy for bucket " + bucket);
// S3
objectLifecycleVisibilityRemover.removeVisibility(bucket);
// H2
var spaceEntity = spaceByNameRetriever.retrieveByName(space);
spaceEntity.setTtl(null);
spaceSaver.save(spaceEntity);
}
}

View File

@@ -0,0 +1,15 @@
package io.jgoerner.s3.domain;
import lombok.Builder;
import lombok.Value;
import java.net.URL;
@Value
@Builder
public class Object {
String name;
String key;
URL url;
@Builder.Default boolean isPublic = false;
}

View File

@@ -0,0 +1,12 @@
package io.jgoerner.s3.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ObjectPartial {
Boolean isPublic;
}

View File

@@ -0,0 +1,12 @@
package io.jgoerner.s3.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor
@Data
public class Space {
String name;
String bucket;
Integer ttl;
}

View File

@@ -0,0 +1,12 @@
package io.jgoerner.s3.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class SpacePartial {
Integer ttlInDays;
}

View File

@@ -0,0 +1,36 @@
spring:
datasource:
url: jdbc:h2:./data/metadb
username: admin
h2:
console:
enabled: true
path: /h2
jpa:
hibernate:
ddl-auto: update
# https://stackoverflow.com/questions/40057057/spring-boot-and-thymeleaf-hot-swap-templates-and-resources-once-again
thymeleaf:
cache: false
templates_root: src/main/resources/templates/
servlet:
multipart:
max-file-size: 15MB
max-request-size: 15MB
cloud:
aws:
region:
static: eu-central-1
stack:
auto: false
credentials:
profile-name: dev
# https://docs.spring.io/spring-cloud-aws/docs/2.2.3.RELEASE/reference/html/#amazon-sdk-configuration
logging:
level:
com:
amazonaws:
util:
EC2MetadataUtils: error

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
<head th:fragment="head" xmlns:th="http://www.w3.org/1999/xhtml">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>File Share</title>
<link rel="stylesheet" th:href="@{/css/bulma.min.css}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css">
</head>

View File

@@ -0,0 +1,43 @@
<nav class="navbar" th:fragment="navbar" xmlns:th="http://www.w3.org/1999/xhtml">
<div class=" container">
<div class="navbar-brand">
<a class="navbar-item" href="/">
<span class="icon-text">
<span class="icon">
<i class="fas fa-home"></i>
</span>
<span>Home</span>
</span>
</a>
<a class="navbar-item" th:href="@{~/space}">
<span class="icon-text">
<span class="icon">
<i class="fas fa-folder-open"></i>
</span>
<span>Spaces</span>
</span>
</a>
</div>
<div class="navbar-menu">
<div class="navbar-end">
<a class="navbar-item" href="h2" target="_blank">
<span class="icon-text">
<span class="icon">
<i class="fas fa-database"></i>
</span>
<span>DB</span>
</span>
</a>
<span class="navbar-item">
<a class="button is-primary" href="https://github.com/jgoerner" target="_blank">
<span class="icon">
<i class="fab fa-github"></i>
</span>
<span>GitHub</span>
</a>
</span>
</div>
</div>
</div>
</nav>

View File

@@ -0,0 +1,23 @@
<script th:inline="javascript" th:fragment="script" xmlns:th="http://www.w3.org/1999/xhtml">
document.addEventListener('DOMContentLoaded', () => {
(document.querySelectorAll('.notification .delete') || []).forEach(($delete) => {
var $notification = $delete.parentNode;
$delete.addEventListener('click', () => {
$notification.parentNode.removeChild($notification);
});
});
});
</script>
<div th:fragment="notification" xmlns:th="http://www.w3.org/1999/xhtml">
<div class="notification" th:if="${success}">
<button class="delete"></button>
<p class="has-text-grey-light" th:text="${success}"/>
</div>
<div class="notification is-warning is-light" th:if="${warn}">
<button class="delete"></button>
<p th:text="${warn}"/>
</div>
</div>

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/head :: head"/>
<body>
<div th:replace="fragments/navbar :: navbar"/>
<section class="hero is-primary is-fullheight-with-navbar is-bold">
<div class="hero-body">
<div class="container has-text-centered">
<h1 class="title">
Private File Share
</h1>
<h2 class="subtitle">
powered by Spring Boot & Simple Storage Service (S3)
</h2>
</div>
</div>
</section>
</body>
</html>

View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/head :: head"/>
<body>
<div th:replace="fragments/navbar :: navbar"/>
<section class="hero is-fullheight-with-navbar is-primary is-bold">
<section class="hero-body columns is-centered m-0">
<div class="column is-half ">
<form action="#" th:action="@{/space/__${space}__/object-form}" th:object="${object}" method="post"
enctype="multipart/form-data">
<div class="field">
<div class="control has-icons-left">
<input class="input is-medium" type="text" th:field="*{name}" placeholder="Name of your File">
<span class="icon is-small is-left">
<i class="fas fa-heading"></i>
</span>
</div>
<p class="help">Human readable name of the file</p>
</div>
<div class="field">
<div class="control">
<input type="file" name="file"/>
</div>
<p class="help">Human readable name of the file</p>
</div>
<div class="field is-grouped is-grouped-centered">
<p class="control">
<button class="button is-primary is-outlined is-inverted" type="submit">
Submit
</button>
</p>
<p class="control">
<a class="button is-primary is-outlined is-inverted" href="#">
Cancel
</a>
</p>
</div>
</form>
</div>
</section>
</section>
</body>
</html>

View File

@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/head :: head"/>
<body>
<div th:replace="fragments/navbar :: navbar"/>
<section class="hero is-fullheight-with-navbar is-primary is-bold">
<section class="hero-body columns is-centered m-0">
<div class="column is-half has-text-centered">
<div th:replace="fragments/notification :: notification"/>
<div th:each="object: ${objects}" class="card m-4">
<div class="card-content">
<div class="media">
<div class="media-content">
<p class="title is-4 has-text-dark" th:text="${object.name}"/>
<p class="subtitle is-6 has-text-grey-light mb-1" th:text="${object.key}"/>
<p th:if="${object.isPublic}" class="subtitle is-6 has-text-grey-light">
Publicly accessible
</p>
<p th:unless="${object.isPublic}" class="subtitle is-6 has-text-grey-light">Private</p>
</div>
</div>
</div>
<div class="card-footer">
<a th:href="${object.url}" target="_blank"
class="card-footer-item has-text-info">
<span class="icon is-small pr-3">
<i class="fas fa-download"></i>
</span>
<span>Download</span>
</a>
<a th:if="${object.isPublic}" th:href="@{~/space/__${space}__/make-private/__${object.key}__}"
class="card-footer-item has-text-info">
<span class="icon is-small pr-3">
<i class="fas fa-eye-slash"></i>
</span>
<span>Make Private</span>
</a>
<a th:unless="${object.isPublic}" th:href="@{~/space/__${space}__/make-public/__${object.key}__}"
class="card-footer-item has-text-info">
<span class="icon is-small pr-3">
<i class="fas fa-eye"></i>
</span>
<span>Make Public</span>
</a>
<a th:href="@{~/space/__${space}__/magic/__${object.key}__}"
class="card-footer-item has-text-info">
<span class="icon is-small pr-3">
<i class="fas fa-magic"></i>
</span>
<span>Magic Link</span>
</a>
<a th:href="@{~/space/__${space}__/delete/__${object.key}__}"
class="card-footer-item has-text-danger">
<span class="icon is-small pr-3">
<i class="fas fa-trash-alt"></i>
</span>
<span>Delete</span>
</a>
</div>
</div>
<button class="button is-medium is-primary is-inverted is-outlined ">
<a th:href="@{~/space/__${space}__/object-form}">
<span class="icon is-small">
<i class="fas fa-upload"></i>
</span>
<span>Upload File</span>
</a>
</button>
</div>
</section>
</section>
<script th:replace="fragments/notification :: script"/>
</body>
</html>

View File

@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/head :: head"/>
<body>
<div th:replace="fragments/navbar :: navbar"/>
<section class="hero is-fullheight-with-navbar is-primary is-bold">
<section class="hero-body columns is-centered m-0">
<div class="column is-half ">
<form action="#" th:action="@{/space-form}" th:object="${space}" method="post">
<div class="field">
<div class="control has-icons-left">
<input class="input is-medium" type="text" th:field="*{name}" placeholder="Name of your space">
<span class="icon is-small is-left">
<i class="fas fa-heading"></i>
</span>
</div>
<p class="help">Human readable name of the bucket</p>
</div>
<div class="field is-grouped is-grouped-centered">
<p class="control">
<button class="button is-primary is-outlined is-inverted" type="submit">
Submit
</button>
</p>
<p class="control">
<a class="button is-primary is-outlined is-inverted" href="space">
Cancel
</a>
</p>
</div>
</form>
</div>
</section>
</section>
</body>
</html>

View File

@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/head :: head"/>
<body>
<div th:replace="fragments/navbar :: navbar"/>
<section class="hero is-fullheight-with-navbar is-primary is-bold">
<section class="hero-body columns is-centered m-0">
<div class="column is-half has-text-centered">
<div th:replace="fragments/notification :: notification"/>
<div th:each="space: ${spaces}" class="card m-4">
<div class="card-content">
<div class="media">
<div class="media-content">
<p class="title is-4 has-text-dark" th:text="${space.name}"/>
<p class="subtitle is-6 has-text-grey-light mb-1" th:text="${space.bucket}"/>
<p th:if=${space.ttl} class="subtitle is-6 has-text-grey-light"
th:text="${'Only storing files for ' + space.ttl + ' days'}"/>
<p th:unless=${space.ttl} class="subtitle is-6 has-text-grey-light"
>Permanent Storage</p>
</div>
</div>
</div>
<div class="card-footer">
<a th:href="@{space/__${space.name}__}"
class="card-footer-item has-text-info">
<span class="icon is-small pr-3">
<i class="fas fa-info"></i>
</span>
<span>Details</span>
</a>
<a th:unless="${space.ttl}" th:href="@{space/temporary/__${space.name}__}"
class="card-footer-item has-text-info">
<span class="icon is-small pr-3">
<i class="fas fa-hourglass-half"></i>
</span>
<span>Make Temporary</span>
</a>
<a th:if="${space.ttl}" th:href="@{space/permanent/__${space.name}__}"
class="card-footer-item has-text-info">
<span class="icon is-small pr-3">
<i class="fas fa-infinity"></i>
</span>
<span>Make Permanent</span>
</a>
<a th:href="@{space/delete/__${space.name}__}"
class="card-footer-item has-text-danger">
<span class="icon is-small pr-3">
<i class="fas fa-trash-alt"></i>
</span>
<span>Delete</span>
</a>
</div>
</div>
<button class="button is-medium is-primary is-inverted is-outlined ">
<a href="space-form">
<span class="icon is-small">
<i class="fas fa-plus-square"></i>
</span>
<span>New Space</span>
</a>
</button>
</div>
</section>
</section>
<script th:replace="fragments/notification :: script"/>
</body>
</html>

View File

@@ -0,0 +1,13 @@
package io.jgoerner.s3;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class S3ApplicationTests {
@Test
void contextLoads() {
}
}

View File

@@ -0,0 +1,5 @@
FROM --platform=linux/amd64 adoptopenjdk/openjdk11:jre-11.0.10_9-alpine
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} service.jar
EXPOSE 8080
ENTRYPOINT [ "sh", "-c", "java -jar -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 service.jar" ]

View File

@@ -0,0 +1,4 @@
# Caching In Spring Boot Application with ElastiCache for Redis
* This example showcases how you can configure spring cache and connect it with
AWS ElastiCache

View File

@@ -0,0 +1,44 @@
plugins {
id 'org.springframework.boot' version '2.4.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'io.reflectoring'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'io.awspring.cloud:spring-cloud-starter-aws'
// implementation 'com.amazonaws:aws-java-sdk-cloudformation'
implementation 'com.amazonaws:aws-java-sdk-elasticache'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
imports {
mavenBom 'io.awspring.cloud:spring-cloud-aws-dependencies:2.3.1'
}
}
test {
useJUnitPlatform()
}

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
aws/spring-cloud-caching-redis/gradlew vendored Executable file
View File

@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or 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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# 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"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# 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
;;
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"
which java >/dev/null 2>&1 || 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
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

View File

@@ -0,0 +1,89 @@
@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
@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=.
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%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
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%"=="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!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1 @@
rootProject.name = 'spring-cloud-redis'

View File

@@ -0,0 +1,12 @@
package io.reflectoring.springcloudredis;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringCloudRedisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudRedisApplication.class, args);
}
}

View File

@@ -0,0 +1,10 @@
package io.reflectoring.springcloudredis.configuration;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class EnableCache {
}

View File

@@ -0,0 +1,41 @@
package io.reflectoring.springcloudredis.controller;
import io.reflectoring.springcloudredis.entity.Product;
import io.reflectoring.springcloudredis.model.ProductInput;
import io.reflectoring.springcloudredis.service.ProductService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@Slf4j(topic = "PRODUCT_CONTROLLER")
@RestController
@RequestMapping("/product")
@AllArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping("/{id}")
public Product getProduct(@PathVariable String id){
return productService.getProduct(id);
}
@DeleteMapping("/{id}")
public void deleteProduct(@PathVariable String id){
productService.deleteProduct(id);
}
@PutMapping("/{id}")
public Product updateProduct(@PathVariable String id, @RequestBody @Valid ProductInput input){
return productService.updateProduct(id, input);
}
@PostMapping
public Product addProduct(@RequestBody @Valid ProductInput input){
return productService.addProduct(input);
}
}

Some files were not shown because too many files have changed in this diff Show More