├── vercel.json ├── styles └── tailwind.css ├── .eslintrc ├── public └── favicon.ico ├── remix.env.d.ts ├── app ├── entry.client.tsx ├── entry.server.tsx ├── root.tsx └── routes │ └── index.tsx ├── server.js ├── tailwind.config.js ├── remix.config.js ├── tsconfig.json ├── .gitignore ├── package.json ├── go.mod ├── api ├── avatar.go └── getpic.go ├── README.md └── go.sum /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "silent": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@remix-run/eslint-config", "@remix-run/eslint-config/node"] 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevClad-Inc/serverless-userpics/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /remix.env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | import { RemixBrowser } from "@remix-run/react"; 2 | import { hydrateRoot } from "react-dom/client"; 3 | 4 | hydrateRoot(document, ); 5 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import { createRequestHandler } from "@remix-run/vercel"; 2 | import * as build from "@remix-run/dev/server-build"; 3 | 4 | export default createRequestHandler({ build, mode: process.env.NODE_ENV }); 5 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | 3 | module.exports = { 4 | content: [ 5 | "./app/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: ['@tailwindcss/forms', ], 11 | } 12 | -------------------------------------------------------------------------------- /remix.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@remix-run/dev').AppConfig} */ 2 | module.exports = { 3 | serverBuildTarget: "vercel", 4 | // When running locally in development mode, we use the built in remix 5 | // server. This does not understand the vercel lambda module format, 6 | // so we default back to the standard build output. 7 | server: process.env.NODE_ENV === "development" ? undefined : "./server.js", 8 | ignoredRouteFiles: ["**/.*"], 9 | // appDirectory: "app", 10 | // assetsBuildDirectory: "public/build", 11 | // serverBuildPath: "api/index.js", 12 | // publicPath: "/build/", 13 | }; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], 3 | "compilerOptions": { 4 | "lib": ["DOM", "DOM.Iterable", "ES2019"], 5 | "isolatedModules": true, 6 | "esModuleInterop": true, 7 | "jsx": "react-jsx", 8 | "moduleResolution": "node", 9 | "resolveJsonModule": true, 10 | "target": "ES2019", 11 | "strict": true, 12 | "allowJs": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "baseUrl": ".", 15 | "paths": { 16 | "~/*": ["./app/*"] 17 | }, 18 | 19 | // Remix takes care of building everything in `remix build`. 20 | "noEmit": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/entry.server.tsx: -------------------------------------------------------------------------------- 1 | import type { EntryContext } from "@remix-run/node"; 2 | import { RemixServer } from "@remix-run/react"; 3 | import { renderToString } from "react-dom/server"; 4 | 5 | export default function handleRequest( 6 | request: Request, 7 | responseStatusCode: number, 8 | responseHeaders: Headers, 9 | remixContext: EntryContext 10 | ) { 11 | const markup = renderToString( 12 | 13 | ); 14 | 15 | responseHeaders.set("Content-Type", "text/html"); 16 | 17 | return new Response("" + markup, { 18 | status: responseStatusCode, 19 | headers: responseHeaders, 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /app/root.tsx: -------------------------------------------------------------------------------- 1 | import type { MetaFunction } from "@remix-run/node"; 2 | import styles from "./styles/tailwind.css"; 3 | import { 4 | Links, 5 | LiveReload, 6 | Meta, 7 | Outlet, 8 | Scripts, 9 | ScrollRestoration, 10 | } from "@remix-run/react"; 11 | 12 | export function links() { 13 | return [ 14 | { rel: "stylesheet", 15 | href: styles 16 | }, 17 | { 18 | rel: "icon", 19 | href: "/favicon.ico", 20 | type: "image/ico", 21 | }, 22 | ] 23 | } 24 | 25 | export const meta: MetaFunction = () => ({ 26 | charset: "utf-8", 27 | title: "Userpics by DevClad - Avatars in 1 API Call", 28 | viewport: "width=device-width,initial-scale=1", 29 | }); 30 | 31 | export default function App() { 32 | return ( 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | # Logs 24 | logs 25 | *.log 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | pnpm-debug.log* 30 | lerna-debug.log* 31 | 32 | node_modules 33 | dist 34 | dist-ssr 35 | *.local 36 | 37 | # Editor directories and files 38 | .vscode/* 39 | !.vscode/extensions.json 40 | .idea 41 | .DS_Store 42 | *.suo 43 | *.ntvs* 44 | *.njsproj 45 | *.sln 46 | *.sw? 47 | 48 | .cache 49 | .env 50 | .vercel 51 | .output 52 | 53 | /build/ 54 | /public/build 55 | /api/index.js 56 | /api/index.js.map 57 | 58 | /app/styles/tailwind.css 59 | /api/_assets 60 | /api/index.js 61 | /api/index.js.map 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "sideEffects": false, 4 | "scripts": { 5 | "setup": "yarn vercel login && yarn vercel link", 6 | "start": "yarn vercel dev", 7 | "build": "yarn run build:css && remix build", 8 | "build:css": "tailwindcss -m -i ./styles/tailwind.css -o app/styles/tailwind.css", 9 | "dev": "concurrently \"yarn run dev:css\" \"remix dev\"", 10 | "dev:css": "tailwindcss -w -i ./styles/tailwind.css -o app/styles/tailwind.css" 11 | }, 12 | "dependencies": { 13 | "@heroicons/react": "^2.0.11", 14 | "@remix-run/node": "^1.7.2", 15 | "@remix-run/react": "^1.7.2", 16 | "@remix-run/vercel": "^1.7.2", 17 | "@vercel/node": "^2.4.4", 18 | "react": "^18.2.0", 19 | "react-dom": "^18.2.0" 20 | }, 21 | "devDependencies": { 22 | "@remix-run/dev": "^1.7.2", 23 | "@remix-run/eslint-config": "^1.7.2", 24 | "@remix-run/serve": "^1.7.2", 25 | "@types/react": "^18.0.15", 26 | "@types/react-dom": "^18.0.6", 27 | "autoprefixer": "^10.4.12", 28 | "concurrently": "^7.4.0", 29 | "eslint": "^8.23.1", 30 | "postcss": "^8.4.16", 31 | "tailwindcss": "^3.1.8", 32 | "typescript": "^4.7.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/arthtyagi/serverless-userpics 2 | 3 | // go 1.19 4 | go 1.18 5 | 6 | require ( 7 | github.com/aws/aws-sdk-go-v2 v1.16.16 8 | github.com/aws/aws-sdk-go-v2/config v1.17.7 9 | github.com/aws/aws-sdk-go-v2/credentials v1.12.20 10 | github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11 11 | ) 12 | 13 | require ( 14 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 // indirect 15 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17 // indirect 16 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 // indirect 17 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 // indirect 18 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24 // indirect 19 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 // indirect 20 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 // indirect 21 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 // indirect 22 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17 // indirect 23 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17 // indirect 24 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.23 // indirect 25 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.5 // indirect 26 | github.com/aws/aws-sdk-go-v2/service/sts v1.16.19 // indirect 27 | github.com/aws/smithy-go v1.13.3 // indirect 28 | ) 29 | 30 | // ignore node_modules 31 | -------------------------------------------------------------------------------- /api/avatar.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/http" 8 | "strconv" 9 | "sync" 10 | ) 11 | 12 | type BoringParams struct { 13 | size int 14 | name string 15 | variant string 16 | } 17 | 18 | func Boring (w http.ResponseWriter, r *http.Request) { 19 | 20 | base_url := "https://source.boringavatars.com/" 21 | 22 | // default values 23 | size := 128 24 | name := "Cactus Jack" 25 | variant := "beam" 26 | 27 | stream := false 28 | 29 | if r.URL.Query().Get("size") != "" { 30 | s, err := strconv.Atoi(r.URL.Query().Get("size")) 31 | if err != nil { 32 | log.Fatal(err) 33 | } else { 34 | size = s 35 | } 36 | } 37 | if r.URL.Query().Get("name") != "" { 38 | name = r.URL.Query().Get("name") 39 | } 40 | if r.URL.Query().Get("variant") != "" { 41 | variant = r.URL.Query().Get("variant") 42 | } 43 | 44 | if r.URL.Query().Get("stream") != "" { 45 | stream = true 46 | } 47 | 48 | // todo: add color support 49 | params := BoringParams{ 50 | size: size, 51 | name: name, 52 | variant: variant, 53 | } 54 | // why a waitgroup? cause fuck you, i like goroutines 55 | wg := sync.WaitGroup{} 56 | wg.Add(1) 57 | 58 | go func() { 59 | defer wg.Done() 60 | if stream { 61 | // display the image 62 | StreamBoringPic(w, r, FetchBoring(params, base_url)) 63 | } else { 64 | fmt.Fprintln(w, "URL:", FetchBoring(params, base_url)) 65 | fmt.Fprintf(w, "Query Params: size=%d, name=%s, variant=%s, stream=%t", size, name, variant, stream) 66 | } 67 | }() 68 | 69 | wg.Wait() 70 | 71 | } 72 | 73 | func FetchBoring (params BoringParams, base_url string) string { 74 | 75 | base_url += params.variant + "/" + strconv.Itoa(params.size) + "/" + params.name 76 | 77 | return base_url 78 | } 79 | 80 | func StreamBoringPic (w http.ResponseWriter, r *http.Request, url string) int64 { 81 | // download the image 82 | resp, err := http.Get(url) 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | 87 | // stream the image 88 | w.Header().Set("Content-Type", "image/svg") 89 | 90 | res, err := io.Copy(w, resp.Body) 91 | 92 | if err != nil { 93 | log.Fatal(err) 94 | } else { 95 | return res 96 | } 97 | 98 | return res 99 | } -------------------------------------------------------------------------------- /app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Index() { 2 | return ( 3 |
4 |
5 |

Serverless Userpics

6 |
7 |
8 | 9 | 10 | 11 | View on GitHub 12 | 13 | 20 | 21 | Get a random userpic (Fetch) 22 | Get an avatar (Fetch) 23 | 24 | 25 |
26 | 27 |

Instructions

28 |
29 | 30 | Please check out the 31 | 35 | readme 36 | 37 | on GitHub for instructions on how to use this. (It's easy!) 38 | 39 |
40 | 41 | 53 |
54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /api/getpic.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "math/rand" 9 | "net/http" 10 | "os" 11 | 12 | // "path/filepath" 13 | "sync" 14 | "time" 15 | 16 | // external 17 | "github.com/aws/aws-sdk-go-v2/aws" 18 | "github.com/aws/aws-sdk-go-v2/config" 19 | "github.com/aws/aws-sdk-go-v2/credentials" 20 | "github.com/aws/aws-sdk-go-v2/service/s3" 21 | // "github.com/joho/godotenv" 22 | ) 23 | 24 | type S3Response struct { 25 | Contents []struct { 26 | Key string `json:"Key"` 27 | } `json:"Contents"` 28 | } 29 | 30 | // uncomment some code to test locally 31 | 32 | // func main() { 33 | // GetPic() 34 | // } 35 | 36 | func GetPic(w http.ResponseWriter, r *http.Request) { 37 | // err := godotenv.Load(filepath.Join("api/.env")) 38 | // fmt.Println("Loading .env file", err) 39 | // if err != nil { log.Fatal("Error loading .env file") } 40 | 41 | // ENV INITIALIZATION 42 | accountId := os.Getenv("ACCOUNT_ID") 43 | bucketName := os.Getenv("BUCKET_NAME") 44 | accessKeyId := os.Getenv("API_ACCESS_KEY") 45 | accessKeySecret := os.Getenv("API_SECRET_KEY") 46 | 47 | // === initialization shit === 48 | 49 | // AWS CONFIGURATION (using V2 SDK - https://developers.cloudflare.com/r2/examples/aws-sdk-go/ gives a neat example btw) 50 | r2Resolver := aws.EndpointResolverWithOptionsFunc( 51 | func(service, region string, options ...interface{}, 52 | ) (aws.Endpoint, error) { 53 | return aws.Endpoint{ 54 | URL: fmt.Sprintf("https://%s.r2.cloudflarestorage.com", accountId), 55 | }, nil 56 | }) 57 | 58 | 59 | cfg, err := config.LoadDefaultConfig(context.TODO(), 60 | config.WithEndpointResolverWithOptions(r2Resolver), 61 | config.WithCredentialsProvider( 62 | credentials.NewStaticCredentialsProvider( 63 | accessKeyId, accessKeySecret, "", 64 | ), 65 | ), 66 | ) 67 | if err != nil {log.Fatal(err)} 68 | 69 | client := s3.NewFromConfig(cfg) 70 | 71 | // test out 72 | // fmt.Println(ImageList(client, bucketName, accountId)) 73 | 74 | wg := sync.WaitGroup{} 75 | wg.Add(1) 76 | go func() { 77 | defer wg.Done() 78 | fmt.Fprintf(w, ImageList(client, bucketName, accountId)) 79 | // ImageList(client, bucketName, accountId) 80 | }() 81 | 82 | wg.Wait() 83 | 84 | } 85 | 86 | func ImageList(client *s3.Client, bucketName string, accountId string) string { 87 | publicId := os.Getenv("PUBLIC_ID") 88 | // LIST OF IMAGES 89 | // (https://developers.cloudflare.com/r2/data-access/s3-api/api/#implemented-object-level-operations/) has list of available operations 90 | 91 | input := &s3.ListObjectsV2Input{ 92 | Bucket: aws.String(bucketName), 93 | } 94 | 95 | result, err := client.ListObjectsV2(context.TODO(), input) 96 | if err != nil {log.Fatal(err)} 97 | 98 | // RESPONSE HANDLING 99 | 100 | var s3Response S3Response 101 | jsonString, err := json.Marshal(result) 102 | if err != nil {log.Fatal(err)} 103 | json.Unmarshal(jsonString, &s3Response) 104 | 105 | // i have to use time as a source?! lol python is better hAhAhA (ofc not wtf) 106 | goddamnSource := rand.NewSource(time.Now().UnixNano()) 107 | r := rand.New(goddamnSource) 108 | randomNumber := r.Intn(len(s3Response.Contents)) 109 | randomImage := s3Response.Contents[randomNumber].Key 110 | generatedURl := fmt.Sprintf("https://%s.r2.dev/%s", publicId, randomImage) 111 | return generatedURl 112 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Serverless Userpics 2 | 3 | **Fetch avatars for your next service in a single API call.** 4 | 5 | Development has moved to the [DevClad Mono[turbo]repo](https://github.com/DevClad-Inc/devclad/tree/main/apps/userpics) 6 | 7 | ## Features 8 | 9 | - [x] 🚀 Generate avatars on the fly 10 | - [X] 🎨 100 avatars included from [Craftwork.design](https://craftwork.design/) (I'm not affiliated with them, just a fan) 11 | - [X] 🎨 Abstraction over [Boring Avatars](https://boringavatars.com/) included as well. 12 | - [x] ✨ Tiny as fuck. 13 | - [x] ✨ Serverless. Configurable. Easily deployable. 14 | 15 | ## Usage (Important) 16 | 17 | ### Routes 18 | 19 | 1. `api/getpic/` 20 | 2. `api/avatar/` - `?stream=True` query outputs an SVG for you. 21 | 22 | #### Available Query Params 23 | 24 | - `?name=string` - Your username. Defaults to `Cactus Jack`. 25 | - `?size=int` - Size of the avatar. Default is 128. 26 | - `?variant=string` - Variant of the avatar. Default is `beam`. 27 | - `?stream=bool` - Stream the SVG instead of returning a URL. Default is False. 28 | 29 | #### Available Variants 30 | 31 | - `marble` 32 | - `pixel` 33 | - `beam` 34 | - `sunset` 35 | - `ring` 36 | - `bauhaus` 37 | 38 | All these variants and avatars (under `api/avatar/` route) are from [Boring Avatars](https://boringavatars.com/). 39 | 40 | ![Route 2](https://imagedelivery.net/nF-ES6OEyyKZDJvRdLK8oA/dff96186-fdb4-4d1c-094a-4b63eaa7f100/public) 41 | 42 | ### Quick and dirty example 43 | 44 | 1. Head over to [userpics.devclad.com](https://userpics.devclad.com) and "Get a random user pic". 45 | 2. Use the URL in your app. 46 | 47 | ### Python Example 48 | 49 | - `/api/getpic/` is the endpoint to get a random avatar. 50 | 51 | ``` python 52 | def random_avatar(): 53 | name = str(uuid.uuid4())[:8] 54 | with open(f"media/avatars/{name}.png", "wb+") as f: 55 | url = requests.get("https://userpics.devclad.com/api/getpic") 56 | response = requests.get(url.text, stream=True) 57 | if not response.ok: 58 | raise Exception("Could not get avatar") 59 | for block in response.iter_content(1024): 60 | if not block: 61 | break 62 | f.write(block) 63 | return f"avatars/{name}.png" 64 | ``` 65 | 66 | You can try svg too btw, haven't tested it but it should work. 67 | 68 | ## Config 69 | 70 | ``` go 71 | accountId := os.Getenv("ACCOUNT_ID") 72 | bucketName := os.Getenv("BUCKET_NAME") 73 | accessKeyId := os.Getenv("API_ACCESS_KEY") 74 | accessKeySecret := os.Getenv("API_SECRET_KEY") 75 | ``` 76 | 77 | ### Replicate this 78 | 79 | 1. Create an R2 bucket on Cloudflare. 80 | 2. Generate S3 Token via `Manage R2 API Tokens` in R2 Dashboard. 81 | 3. Set your environment variables. 82 | 4. Deploy ⚡ 83 | 84 | ### Running locally 85 | 86 | do the usual. install dependencies via `yarn` and run `yarn run start`/`yarn run dev`. 87 | 88 | **Make sure to setup environment variables in your Vercel dashboard.** 89 | **Also make sure to have S3 API access and secret on the recieving end if your bucket is private like mine.** 90 | 91 | ### Services 92 | 93 | | Resource | Service | 94 | | --- | --- | 95 | | Storage | Cloudflare R2 (S3 compatible) | 96 | | Serverless Hosting | Vercel (AWS Lambda) | 97 | 98 | --- 99 | 100 | ### Todo 101 | 102 | Check out the [issues](https://github.com/arthtyagi/serverless-userpics/issues) for more info. 103 | 104 | #### YANKING 105 | 106 | [template-go-vercel](https://github.com/riccardogiorato/template-go-vercel) was helpful while deploying. 107 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go-v2 v1.16.16 h1:M1fj4FE2lB4NzRb9Y0xdWsn2P0+2UHVxwKyOa4YJNjk= 2 | github.com/aws/aws-sdk-go-v2 v1.16.16/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k= 3 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 h1:tcFliCWne+zOuUfKNRn8JdFBuWPDuISDH08wD2ULkhk= 4 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8/go.mod h1:JTnlBSot91steJeti4ryyu/tLd4Sk84O5W22L7O2EQU= 5 | github.com/aws/aws-sdk-go-v2/config v1.17.7 h1:odVM52tFHhpqZBKNjVW5h+Zt1tKHbhdTQRb+0WHrNtw= 6 | github.com/aws/aws-sdk-go-v2/config v1.17.7/go.mod h1:dN2gja/QXxFF15hQreyrqYhLBaQo1d9ZKe/v/uplQoI= 7 | github.com/aws/aws-sdk-go-v2/credentials v1.12.20 h1:9+ZhlDY7N9dPnUmf7CDfW9In4sW5Ff3bh7oy4DzS1IE= 8 | github.com/aws/aws-sdk-go-v2/credentials v1.12.20/go.mod h1:UKY5HyIux08bbNA7Blv4PcXQ8cTkGh7ghHMFklaviR4= 9 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17 h1:r08j4sbZu/RVi+BNxkBJwPMUYY3P8mgSDuKkZ/ZN1lE= 10 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17/go.mod h1:yIkQcCDYNsZfXpd5UX2Cy+sWA1jPgIhGTw9cOBzfVnQ= 11 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 h1:s4g/wnzMf+qepSNgTvaQQHNxyMLKSawNhKCPNy++2xY= 12 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfIRy0imBCJv+vZ2X6RKxves6fbnEuSry6b4= 13 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 h1:/K482T5A3623WJgWT8w1yRAFK4RzGzEl7y39yhtn9eA= 14 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17/go.mod h1:pRwaTYCJemADaqCbUAxltMoHKata7hmB5PjEXeu0kfg= 15 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24 h1:wj5Rwc05hvUSvKuOF29IYb9QrCLjU+rHAy/x/o0DK2c= 16 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24/go.mod h1:jULHjqqjDlbyTa7pfM7WICATnOv+iOhjletM3N0Xbu8= 17 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 h1:ZSIPAkAsCCjYrhqfw2+lNzWDzxzHXEckFkTePL5RSWQ= 18 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14/go.mod h1:AyGgqiKv9ECM6IZeNQtdT8NnMvUb3/2wokeq2Fgryto= 19 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 h1:Lh1AShsuIJTwMkoxVCAYPJgNG5H+eN6SmoUn8nOZ5wE= 20 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9/go.mod h1:a9j48l6yL5XINLHLcOKInjdvknN+vWqPBxqeIDw7ktw= 21 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 h1:BBYoNQt2kUZUUK4bIPsKrCcjVPUMNsgQpNAwhznK/zo= 22 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18/go.mod h1:NS55eQ4YixUJPTC+INxi2/jCqe1y2Uw3rnh9wEOVJxY= 23 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17 h1:Jrd/oMh0PKQc6+BowB+pLEwLIgaQF29eYbe7E1Av9Ug= 24 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17/go.mod h1:4nYOrY41Lrbk2170/BGkcJKBhws9Pfn8MG3aGqjjeFI= 25 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17 h1:HfVVR1vItaG6le+Bpw6P4midjBDMKnjMyZnw9MXYUcE= 26 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17/go.mod h1:YqMdV+gEKCQ59NrB7rzrJdALeBIsYiVi8Inj3+KcqHI= 27 | github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11 h1:3/gm/JTX9bX8CpzTgIlrtYpB3EVBDxyg/GY/QdcIEZw= 28 | github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11/go.mod h1:fmgDANqTUCxciViKl9hb/zD5LFbvPINFRgWhDbR+vZo= 29 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.23 h1:pwvCchFUEnlceKIgPUouBJwK81aCkQ8UDMORfeFtW10= 30 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.23/go.mod h1:/w0eg9IhFGjGyyncHIQrXtU8wvNsTJOP0R6PPj0wf80= 31 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.5 h1:GUnZ62TevLqIoDyHeiWj2P7EqaosgakBKVvWriIdLQY= 32 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.5/go.mod h1:csZuQY65DAdFBt1oIjO5hhBR49kQqop4+lcuCjf2arA= 33 | github.com/aws/aws-sdk-go-v2/service/sts v1.16.19 h1:9pPi0PsFNAGILFfPCk8Y0iyEBGc6lu6OQ97U7hmdesg= 34 | github.com/aws/aws-sdk-go-v2/service/sts v1.16.19/go.mod h1:h4J3oPZQbxLhzGnk+j9dfYHi5qIOVJ5kczZd658/ydM= 35 | github.com/aws/smithy-go v1.13.3 h1:l7LYxGuzK6/K+NzJ2mC+VvLUbae0sL3bXU//04MkmnA= 36 | github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= 37 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 38 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 39 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 40 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 41 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 42 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 43 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 44 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 45 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 46 | --------------------------------------------------------------------------------