Giter Site home page Giter Site logo

cloud-native-todo's Introduction

Cloud native Todo

This repo documents my journey to solidify my cloud native understand. I'll use ChatGPT conversations to learn. This includes building a roadmap, and accomplishing each goal on the roadmap.

Branches

  • 001-Cloud-native
  • 002-Developer-environment-setup
  • 003-Api-test
  • 004-Infra-as-code-and-api-test
  • 005-Deploy-from-GitHub
  • 006-Client-todo
  • 007-Add-client-info
  • 008-MongoDB
  • 009-DevX - Prebuild dev container

Which AI to use?

Generally, I'll use GitHub Copilot from inside Visual Studio Code for answers which are context-aware. If the answers don't align with my current understanding, I'll experiment with the conversation and the code until I think my solution is as cloud native (or cloud-agnostic) as I can get it.

What is cloud native?

To me, cloud native means cloud-agnostic. The tools, processes, and code should generally work on any cloud.

Most of my current experience is on Azure and is Azure native, which uses tools that aren't available on other clouds. This will be my fallback for expediency but won't be my first method to solve a problem.

YouTube playlists

Open dev container

Open this repo in a dev container in Visual Studio Code or in GitHub Codespaces.

Install

To install all workspaces

npm install

To deploy from development environment

Use Azure Developer CLI to deploy

azd auth login
azd deploy

To rerun postdeploy hook

Troubleshooting: if local deploy fails on Playwright test, rerun to postdeploy use following command

azd hooks run postdeploy --debug

Output looks like:

root ➜ /workspaces/cloud-native-todo (dfberry/fix-api-log-location) $ azd hooks run postdeploy --debug
2023/12/19 02:42:16 main.go:54: azd version: 1.5.0 (commit 012ae734904e0c376ce5074605a6d0d3f05789ee)
2023/12/19 02:42:16 main.go:208: using cached latest version: 1.5.0 (expires on: 2023-12-20T02:25:23Z)
2023/12/19 02:42:16 project.go:113: Reading project from file '/workspaces/cloud-native-todo/azure.yaml'
2023/12/19 02:42:16 cobra_builder.go:141: Resolved action 'azd-hooks-run-action'
2023/12/19 02:42:16 middleware.go:124: running middleware 'debug'
2023/12/19 02:42:16 middleware.go:124: running middleware 'experimentation'
    1 passed (1.7s)
  
  To open last HTML report run:
  
    npx playwright show-report
  
  Test completed

  ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────2023/12/19 02:42:19 command_runner.go:307: Run exec: ' /tmp/azd-postdeploy-3726761669.sh' , exit code: 0
Additional env:
   SERVICE_API_TODO_RESOURCE_EXISTS=<redacted>
   AZURE_KEY_VAULT_ENDPOINT=<redacted>
   AZURE_KEY_VAULT_NAME=<redacted>
   AZURE_CONTAINER_REGISTRY_ENDPOINT=<redacted>
   SERVICE_API_TODO_IMAGE_NAME=<redacted>
   AZURE_ENV_NAME=<redacted>
   AZURE_LOCATION=<redacted>
   API_TODO_ENDPOINT=<redacted>
   AZURE_SUBSCRIPTION_ID=<redacted>
-------------------------------------stdout-------------------------------------------
/workspaces/cloud-native-todo
***** Root postdeploy
postdeploy.sh
Getting param 1
ENV_PATH: ./api-todo-test/.env
Remove old .env file
Getting values from azd
Run test at ./api-todo-test

> [email protected] test
> npx playwright test

baseURL https://ca-api-todo-123.redfield-123.eastus2.azurecontainerapps.io

Running 1 test using 1 worker
baseURL https://ca-api-todo-123.redfield-123.eastus2.azurecontainerapps.io
api.spec.ts:11:5 › should get all todos
baseURL https://ca-api-todo-123.redfield-123.eastus2.azurecontainerapps.io
  1 passed (1.7s)

To open last HTML report run:

  npx playwright show-report

Test completed
-------------------------------------stderr-------------------------------------------
  (✓) Done: Running postdeploy command hook for project
  Running postdeploy service hook for api-todo

  ──────────── api-todo: postdeploy hook output ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────


  ***** api-todo postdeploy
2023/12/19 02:42:19 hooks_runner.go:162: Executing script '/tmp/azd-postdeploy-2881386505.sh'
  ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  (✓) Done: Running postdeploy service hook for api-todo
Additional env:
SUCCESS: Your hooks have been run successfully

To setup GitHub repo for deployment

azd pipeline config

Output looks like:

root ➜ /workspaces/cloud-native-todo (dfberry/docs-update) $ azd pipeline config

Configure your GitHub pipeline

? This command requires you to be logged into GitHub. Log in using the GitHub CLI? Yes
? What is your preferred protocol for Git operations? HTTPS
? Authenticate Git with your GitHub credentials? Yes
? How would you like to authenticate GitHub CLI? Login with a web browser

! First copy your one-time code: 1234-1234
Press Enter to open github.com in your browser... 
✓ Authentication complete.
- gh config set -h github.com git_protocol https
✓ Configured git protocol
✓ Logged in as dfberry

  (✓) Done: Checking current directory for Git repository
  (✓) Done: Creating service principal az-dev-12-19-2023-02-51-11 (48dcb0f9-0714-4ae8-9bb2-eb5854f219fb)
  (✓) Done: Federated identity credential for GitHub: subject repo:dfberry/cloud-native-todo:pull_request
  (✓) Done: Federated identity credential for GitHub: subject repo:dfberry/cloud-native-todo:ref:refs/heads/dfberry/docs-update
  (✓) Done: Federated identity credential for GitHub: subject repo:dfberry/cloud-native-todo:ref:refs/heads/main
  (✓) Done: Setting AZURE_SUBSCRIPTION_ID repo variable
  (✓) Done: Setting AZURE_TENANT_ID repo variable
  (✓) Done: Setting AZURE_CLIENT_ID repo variable
  (✓) Done: Setting AZURE_ENV_NAME repo variable
  (✓) Done: Setting AZURE_LOCATION repo variable

  GitHub Action secrets are now configured. You can view GitHub action secrets that were created at this link:
  https://github.com/dfberry/cloud-native-todo/settings/secrets/actions

? Would you like to commit and push your local changes to start the configured CI pipeline? Yes
remote: 
remote: Create a pull request for 'dfberry/docs-update' on GitHub by visiting:
remote:      https://github.com/dfberry/cloud-native-todo/pull/new/dfberry/docs-update
remote: 

  (✓) Done: Pushing changes
  (✓) Done: Queuing pipeline

SUCCESS: Your GitHub pipeline has been configured!
Link to view your new repo: https://github.com/dfberry/cloud-native-todo
Link to view your pipeline status: https://github.com/dfberry/cloud-native-todo/actions

Infra

azd auth login --use-device-code
az login --use-device-code
bash scripts/clean.sh
azd env new todo-a
azd provision
bash scripts/local-service-principal.sh
bash scripts/create-env-docker.sh
azd deploy
  1. Sign into Azure Cloud: az login --use-device-code
  2. Sign into Azure Developer CLI: azd auth login --use-device-code
  3. Clean out old file first: bash scripts/clean.sh
  4. azd env new todo-a # or todo-b
  5. Provision the infrastructure so you have the .env file in .azure folder
  6. Create service principal for use with local development
  7. Create the .env.docker file with other required settings and propagate to packages.
  8. Deploy via azure.yml. The Dockerfile in each package pulls in the .env file for each package and then builds to an image.

cloud-native-todo's People

Contributors

dfberry avatar seesharprun avatar

Watchers

 avatar  avatar

Forkers

seesharprun

cloud-native-todo's Issues

006-related - API test fails

both locally and in CICD on 006-branch finds 1 item when default is 3

test runs from workspace, fails without workspace


> [email protected] pretest
> jest --clearCache npm run lint:fix && npm run build && npm run postbuild

Cleared /tmp/jest_0

> [email protected] build
> tsc


> [email protected] postbuild
> ncp ./src/openapi.yaml ./dist/openapi.yaml


> [email protected] postbuild
> ncp ./src/openapi.yaml ./dist/openapi.yaml


> [email protected] test
> cross-env NODE_ENV=test jest --runInBand --no-cache --detectOpenHandles --forceExit --coverage

 FAIL  src/server.test.ts
  todo
    GET /todo
      ✕ responds with json (41 ms)
    POST /todo
      ✕ responds with the posted todo (24 ms)
      ✕ responds with the error (7 ms)
      ✕ responds with the error when title has over 1000k (12 ms)
      ✕ responds with the error when Todo has extra properties (8 ms)
    PUT /todo/:id
      ✕ updates the todo with the given id (6 ms)
      ✕ responds with the error when malformed Todo (9 ms)
      ✕ responds with the error when id not found (7 ms)
      ✕ responds with the error when Todo has extra properties (13 ms)
      ✕ responds with the error when title has over 1000k (9 ms)
    DELETE /todo/:id
      ✕ deletes the todo with the given id (7 ms)
      ✕ responds with the error when malformed id (8 ms)
      ✕ responds with the error when id not found (6 ms)

  ● todo › GET /todo › responds with json

    expect(received).toBe(expected) // Object.is equality

    Expected: 200
    Received: 500

      20 |     it('responds with json', async () => {
      21 |       const response = await request(app).get('/todo');
    > 22 |       expect(response.statusCode).toBe(StatusCodes.OK);
         |                                   ^
      23 |       expect(response.body).toEqual(
      24 |         expect.arrayContaining([
      25 |           expect.objectContaining({

      at src/server.test.ts:22:35
      at fulfilled (src/server.test.ts:5:58)

  ● todo › POST /todo › responds with the posted todo

    expect(received).toEqual(expected) // deep equality

    - Expected  - 4
    + Received  + 1

    - ObjectContaining {
    -   "id": Any<Number>,
    -   "title": "Test Todo",
    - }
    + Object {}

      37 |       const response = await request(app).post('/todo').send(todo);
      38 |
    > 39 |       expect(response.body).toEqual(
         |                             ^
      40 |         expect.objectContaining({
      41 |           id: expect.any(Number),
      42 |           title: 'Test Todo',

      at src/server.test.ts:39:29
      at fulfilled (src/server.test.ts:5:58)

  ● todo › POST /todo › responds with the error

    expect(received).toEqual(expected) // deep equality

    - Expected  - 3
    + Received  + 1

    - ObjectContaining {
    -   "error": "Invalid todo",
    - }
    + Object {}

      49 |       const response = await request(app).post('/todo').send(todo);
      50 |
    > 51 |       expect(response.body).toEqual(
         |                             ^
      52 |         expect.objectContaining(INVALID_TODO_ERROR)
      53 |       );
      54 |       expect(response.statusCode).toBe(StatusCodes.BAD_REQUEST);

      at src/server.test.ts:51:29
      at fulfilled (src/server.test.ts:5:58)

  ● todo › POST /todo › responds with the error when title has over 1000k

    expect(received).toEqual(expected) // deep equality

    - Expected  - 3
    + Received  + 1

    - ObjectContaining {
    -   "error": "Invalid todo",
    - }
    + Object {}

      58 |       const response = await request(app).post('/todo').send(todo);
      59 |
    > 60 |       expect(response.body).toEqual(
         |                             ^
      61 |         expect.objectContaining(INVALID_TODO_ERROR)
      62 |       );
      63 |       expect(response.statusCode).toBe(StatusCodes.BAD_REQUEST);

      at src/server.test.ts:60:29
      at fulfilled (src/server.test.ts:5:58)

  ● todo › POST /todo › responds with the error when Todo has extra properties

    expect(received).toEqual(expected) // deep equality

    - Expected  - 3
    + Received  + 1

    - ObjectContaining {
    -   "error": "Invalid todo",
    - }
    + Object {}

      68 |       const response = await request(app).post('/todo').send(todo);
      69 |
    > 70 |       expect(response.body).toEqual(
         |                             ^
      71 |         expect.objectContaining(INVALID_TODO_ERROR)
      72 |       );
      73 |       expect(response.statusCode).toBe(StatusCodes.BAD_REQUEST);

      at src/server.test.ts:70:29
      at fulfilled (src/server.test.ts:5:58)

  ● todo › PUT /todo/:id › updates the todo with the given id

    expect(received).toBe(expected) // Object.is equality

    Expected: 202
    Received: 500

      87 |
      88 |       const response = await request(app).put('/todo/1').send(todo);
    > 89 |       expect(response.statusCode).toBe(StatusCodes.ACCEPTED);
         |                                   ^
      90 |       expect(response.body).toEqual(
      91 |         expect.objectContaining({
      92 |           // partial match of title string

      at src/server.test.ts:89:35
      at fulfilled (src/server.test.ts:5:58)

  ● todo › PUT /todo/:id › responds with the error when malformed Todo

    expect(received).toEqual(expected) // deep equality

    - Expected  - 3
    + Received  + 1

    - ObjectContaining {
    -   "error": "Invalid todo",
    - }
    + Object {}

      100 |       const response = await request(app).put('/todo/2').send(todo);
      101 |
    > 102 |       expect(response.body).toEqual(
          |                             ^
      103 |         expect.objectContaining(INVALID_TODO_ERROR)
      104 |       );
      105 |       expect(response.statusCode).toBe(StatusCodes.BAD_REQUEST);

      at src/server.test.ts:102:29
      at fulfilled (src/server.test.ts:5:58)

  ● todo › PUT /todo/:id › responds with the error when id not found

    expect(received).toEqual(expected) // deep equality

    - Expected  - 3
    + Received  + 1

    - ObjectContaining {
    -   "error": StringMatching /Todo not found/,
    - }
    + Object {}

      110 |       const response = await request(app).put('/todo/100').send(todo);
      111 |
    > 112 |       expect(response.body).toEqual(
          |                             ^
      113 |         expect.objectContaining(TODO_NOT_FOUND_ERROR)
      114 |       );
      115 |       expect(response.statusCode).toBe(StatusCodes.NOT_FOUND);

      at src/server.test.ts:112:29
      at fulfilled (src/server.test.ts:5:58)

  ● todo › PUT /todo/:id › responds with the error when Todo has extra properties

    expect(received).toEqual(expected) // deep equality

    - Expected  - 3
    + Received  + 1

    - ObjectContaining {
    -   "error": "Invalid todo",
    - }
    + Object {}

      120 |       const response = await request(app).put('/todo/2').send(todo);
      121 |
    > 122 |       expect(response.body).toEqual(
          |                             ^
      123 |         expect.objectContaining(INVALID_TODO_ERROR)
      124 |       );
      125 |       expect(response.statusCode).toBe(StatusCodes.BAD_REQUEST);

      at src/server.test.ts:122:29
      at fulfilled (src/server.test.ts:5:58)

  ● todo › PUT /todo/:id › responds with the error when title has over 1000k

    expect(received).toEqual(expected) // deep equality

    - Expected  - 3
    + Received  + 1

    - ObjectContaining {
    -   "error": "Invalid todo",
    - }
    + Object {}

      129 |       const response = await request(app).put('/todo/2').send(todo);
      130 |
    > 131 |       expect(response.body).toEqual(
          |                             ^
      132 |         expect.objectContaining(INVALID_TODO_ERROR)
      133 |       );
      134 |       expect(response.statusCode).toBe(StatusCodes.BAD_REQUEST);

      at src/server.test.ts:131:29
      at fulfilled (src/server.test.ts:5:58)

  ● todo › DELETE /todo/:id › deletes the todo with the given id

    expect(received).toEqual(expected) // deep equality

    - Expected  - 3
    + Received  + 1

    - ObjectContaining {
    -   "id": 1,
    - }
    + Object {}

      142 |     it('deletes the todo with the given id', async () => {
      143 |       const response = await request(app).delete('/todo/1');
    > 144 |       expect(response.body).toEqual(expect.objectContaining({ id: 1 }));
          |                             ^
      145 |       expect(response.statusCode).toBe(StatusCodes.ACCEPTED);
      146 |     });
      147 |     it('responds with the error when malformed id', async () => {

      at src/server.test.ts:144:29
      at fulfilled (src/server.test.ts:5:58)

  ● todo › DELETE /todo/:id › responds with the error when malformed id

    expect(received).toEqual(expected) // deep equality

    - Expected  - 3
    + Received  + 1

    - ObjectContaining {
    -   "error": "Invalid id",
    - }
    + Object {}

      148 |       const response = await request(app).delete('/todo/dog');
      149 |
    > 150 |       expect(response.body).toEqual(expect.objectContaining(INVALID_ID_ERROR));
          |                             ^
      151 |       expect(response.statusCode).toBe(StatusCodes.BAD_REQUEST);
      152 |     });
      153 |     it('responds with the error when id not found', async () => {

      at src/server.test.ts:150:29
      at fulfilled (src/server.test.ts:5:58)

  ● todo › DELETE /todo/:id › responds with the error when id not found

    expect(received).toEqual(expected) // deep equality

    - Expected  - 3
    + Received  + 1

    - ObjectContaining {
    -   "error": StringMatching /Todo not found/,
    - }
    + Object {}

      154 |       const response = await request(app).delete('/todo/100');
      155 |
    > 156 |       expect(response.body).toEqual(
          |                             ^
      157 |         expect.objectContaining(TODO_NOT_FOUND_ERROR)
      158 |       );
      159 |       expect(response.statusCode).toBe(StatusCodes.NOT_FOUND);

      at src/server.test.ts:156:29
      at fulfilled (src/server.test.ts:5:58)

-|---------|----------|---------|---------|-------------------
 | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-|---------|----------|---------|---------|-------------------
 |   44.85 |        0 |    9.09 |   42.51 |                   
  |   52.83 |        0 |   14.28 |   51.92 |                   
   |      20 |        0 |       0 |   15.78 | 6-47              
   |   72.72 |        0 |      25 |   72.72 | 26,35,40-42,48-53 
  |   46.34 |        0 |    12.5 |   39.39 |                   
   |   46.34 |        0 |    12.5 |   39.39 | ...68,72-77,82-89 
  |      50 |      100 |       0 |      50 |                   
   |      50 |      100 |       0 |      50 | 6-7               
  |   31.57 |        0 |       0 |   31.57 |                   
   |   31.57 |        0 |       0 |   31.57 | 18-69             
-|---------|----------|---------|---------|-------------------
Test Suites: 1 failed, 1 total
Tests:       13 failed, 13 total
Snapshots:   0 total
Time:        3.153 s
Ran all test suites.
npm ERR! Lifecycle script `test` failed with error: 
npm ERR! Error: command failed 
npm ERR!   in workspace: [email protected] 
npm ERR!   at location: /workspaces/cloud-native-todo/api-todo 

Playwright - fix expect

await page.goto(url);

// wrong
await page.waitForTimeout(1000)
expect(page.title()).toBe(title)

// right - implements auto retry for you
await except(page).toHaveTitle(title)


example 2 - button

await page.goto(url);
await page.getByRole('link', { name: 'Get started'}).click();
await expect(page.getByRole('heading', {name: 'Installation'})).toBeVisible();

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.