Skip to content

Commit 0c45c0c

Browse files
committed
wip
Signed-off-by: kvmw <mshamsi@broadcom.com>
1 parent 45d1487 commit 0c45c0c

11 files changed

Lines changed: 260 additions & 0 deletions

File tree

.cfignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Ignore everything except what we need
2+
*
3+
!greeter-messages/
4+
!greeter/
5+
!manifest.yml

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Service Registry Node.js sample
2+
3+
![CI](https://github.com/spring-cloud-services-samples/greeting-nodejs/actions/workflows/ci.yml/badge.svg)
4+
5+
Sample Node.js application demonstrating the use of Service Registry in Tanzu Platform for Cloud Foundry.
6+
7+
For information on the Service Registry product in Tanzu Platform for Cloud Foundry, please [see the documentation](https://techdocs.broadcom.com/us/en/vmware-tanzu/spring/spring-cloud-services-for-cloud-foundry/3-3/scs-tanzu/service-registry-index.html).
8+
9+
## Building and Deploying
10+
11+
- Create a Service Registry instance:
12+
13+
```
14+
cf create-service p.service-registry standard greeter-service-registry
15+
```
16+
17+
- Install dependencies
18+
19+
```
20+
npm install
21+
```
22+
23+
- Push the `greeter-messages` application:
24+
25+
```
26+
cd packages/greeter-messages && cf push
27+
```
28+
29+
- Push the `greeter` application:
30+
31+
```
32+
cd packages/greeter && cf push
33+
```
34+
35+
## Trying It Out
36+
37+
Call `[ROUTE]/hello`, where `[ROUTE]` is the route bound to the `greeter` application, with optional `name` and `salutation` parameters.
38+
39+
```
40+
$ curl -k greeter.apps.example.cf-app.com/hello?name=John
41+
Hello, John!
42+
```

greeter-messages/Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: gunicorn --bind 0.0.0.0:$PORT app:app

greeter-messages/app.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import os
2+
from flask import Flask, request
3+
from eureka_client import EurekaServerClient
4+
5+
6+
app = Flask(__name__)
7+
8+
@app.route('/greeting')
9+
def greeting():
10+
salutation = request.args.get('salutation', 'Hello')
11+
name = request.args.get('name', 'Bob')
12+
13+
return f"{salutation}, {name}!"
14+
15+
if __name__ == '__main__':
16+
port = int(os.getenv("PORT", '8080'))
17+
eureka_client = EurekaServerClient(port)
18+
asyncio.run(eureka_client.start())
19+
app.run(host='0.0.0.0', port=port)

greeter-messages/eureka_client.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from cfenv import AppEnv
2+
from oauth2 import OAuth2Client
3+
from py_eureka_client.eureka_client import EurekaClient
4+
5+
class EurekaServerClient:
6+
def __init__(self, port: int):
7+
self.env = AppEnv()
8+
self.oauth_client = None
9+
self.credentials = None
10+
11+
service_registry = self.env.get_service(label="p.service-registry")
12+
if not service_registry:
13+
raise ValueError("No 'p.service-registry' service instance found.")
14+
15+
self.credentials = service_registry.credentials
16+
self.oauth_client = OAuth2Client(
17+
client_id=self.credentials['client_id'],
18+
client_secret=self.credentials['client_secret'],
19+
access_token_uri=self.credentials['access_token_uri'],
20+
on_err=self.on_err
21+
)
22+
23+
self.client = EurekaClient(
24+
app_name=self.env.name,
25+
instance_port=port,
26+
eureka_server=self.credentials['uri']
27+
)
28+
29+
def on_err(err_type: str, err: Exception):
30+
print(f"{err_type}::{err}")
31+
32+
async def start(self):
33+
await self.client.start()
34+
35+
async def stop(self):
36+
await self.client.stop()

greeter-messages/oauth2.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import time
2+
from typing import Optional
3+
import requests
4+
5+
class OAuth2Client:
6+
"""OAuth 2.0 client for handling client credentials flow with token caching"""
7+
8+
def __init__(self, client_id: str, client_secret: str, access_token_uri: str):
9+
self.client_id = client_id
10+
self.client_secret = client_secret
11+
self.access_token_uri = access_token_uri
12+
self.access_token: Optional[str] = None
13+
self.token_expires_at: Optional[float] = None
14+
15+
def _is_token_expired(self, buffer_seconds: int = 30) -> bool:
16+
"""Check if the current token is expired or will expire soon"""
17+
if not self.access_token or not self.token_expires_at:
18+
return True
19+
20+
# Add buffer time to refresh token before it actually expires
21+
return time.time() >= (self.token_expires_at - buffer_seconds)
22+
23+
def _request_new_token(self) -> str:
24+
"""Request a new access token from the OAuth server"""
25+
# Prepare token request
26+
token_data = {
27+
'grant_type': 'client_credentials',
28+
'client_id': self.client_id,
29+
'client_secret': self.client_secret
30+
}
31+
32+
headers = {
33+
'Content-Type': 'application/x-www-form-urlencoded'
34+
}
35+
36+
try:
37+
response = requests.post(
38+
self.access_token_uri,
39+
data=token_data,
40+
headers=headers,
41+
timeout=30
42+
)
43+
response.raise_for_status()
44+
45+
token_response = response.json()
46+
self.access_token = token_response.get('access_token')
47+
48+
# Calculate expiration time
49+
expires_in = token_response.get('expires_in', 3600) # Default to 1 hour
50+
self.token_expires_at = time.time() + expires_in
51+
52+
if not self.access_token:
53+
raise ValueError("No access token received from OAuth server")
54+
55+
return self.access_token
56+
except requests.exceptions.RequestException as e:
57+
raise ValueError(f"Failed to get access token: {e}") from e
58+
59+
def get_access_token(self) -> str:
60+
"""Get OAuth 2.0 access token using client credentials flow"""
61+
# Check if we have a valid cached token
62+
if not self._is_token_expired():
63+
return self.access_token
64+
# Token is expired or doesn't exist, get a new one
65+
return self._request_new_token()

greeter-messages/requirements.txt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
astroid==3.3.11
2+
blinker==1.9.0
3+
certifi==2025.8.3
4+
cfenv==0.5.3
5+
charset-normalizer==3.4.2
6+
click==8.2.1
7+
dill==0.4.0
8+
dnspython==2.7.0
9+
Flask==3.1.1
10+
furl==2.1.4
11+
gunicorn==23.0.0
12+
idna==3.10
13+
ifaddr==0.2.0
14+
isort==6.0.1
15+
itsdangerous==2.2.0
16+
Jinja2==3.1.6
17+
MarkupSafe==3.0.2
18+
mccabe==0.7.0
19+
orderedmultidict==1.0.1
20+
packaging==25.0
21+
platformdirs==4.3.8
22+
py_eureka_client==0.11.13
23+
pylint==3.3.7
24+
python-dotenv==1.0.0
25+
requests==2.31.0
26+
six==1.17.0
27+
tomlkit==0.13.3
28+
urllib3==2.5.0
29+
Werkzeug==3.1.3

greeter/Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: gunicorn --bind 0.0.0.0:$PORT app:app

greeter/app.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import os
2+
from flask import Flask, request
3+
from shared import EurekaServerClient
4+
5+
app = Flask(__name__)
6+
7+
@app.route('/greet')
8+
def greet():
9+
name = request.args.get('name', 'World')
10+
return f"Greetings, {name}!"
11+
12+
if __name__ == '__main__':
13+
port = int(os.getenv("PORT", '8080'))
14+
eureka_client = EurekaServerClient(port)
15+
eureka_client.start()
16+
app.run(host='0.0.0.0', port=port)

greeter/requirements.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
astroid==3.3.11
2+
blinker==1.9.0
3+
certifi==2025.8.3
4+
cfenv==0.5.3
5+
charset-normalizer==3.4.2
6+
click==8.2.1
7+
dill==0.4.0
8+
Flask==3.1.1
9+
furl==2.1.4
10+
gunicorn==23.0.0
11+
idna==3.10
12+
isort==6.0.1
13+
itsdangerous==2.2.0
14+
Jinja2==3.1.6
15+
MarkupSafe==3.0.2
16+
mccabe==0.7.0
17+
orderedmultidict==1.0.1
18+
packaging==25.0
19+
platformdirs==4.3.8
20+
pylint==3.3.7
21+
python-dotenv==1.0.0
22+
requests==2.31.0
23+
six==1.17.0
24+
tomlkit==0.13.3
25+
urllib3==2.5.0
26+
Werkzeug==3.1.3

0 commit comments

Comments
 (0)