Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 44b261c

Browse files
committed
initial commit: working Python Web service written in Flask that is enabled to register itself automatically to a Spring Boot Admin server
1 parent df7ca4a commit 44b261c

File tree

9 files changed

+351
-1
lines changed

9 files changed

+351
-1
lines changed

‎README.md

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,95 @@
1-
# PythonClientForSpringBootAdmin
1+
# Python Client for Spring Boot Admin
2+
3+
[Spring Boot Admin](https://github.com/codecentric/spring-boot-admin "Spring Boot Admin GitHub repository") is a great tool written to show an admin interface for [Spring Boot<sup>®</sup>](http://projects.spring.io/spring-boot/ "Official Spring-Boot website") applications. However, in a world driven by microservices, other programming languages might also be used to develop components. This project provides a simple executable template of a Python Flask application registering itself as a component to a Spring Boot Admin server. Thereafter, it's monitored by the Spring Boot Admin server (alive status) that also provides access to the shared meta information. Your advantage: Spring Boot and Python applications are monitored using the same tool.
4+
5+
6+
![Python v3.6](https://img.shields.io/badge/python-v3.6-brightgreen) ![Flask v1](https://img.shields.io/badge/flask-v1-brightgreen) [![License](http://img.shields.io/:license-mit-blue.svg?style=flat-square)](http://badges.mit-license.org)
7+
8+
9+
## Getting Started
10+
11+
### Prepare the Project
12+
13+
```sh
14+
git clone https://github.com/anbo-de/PythonClientForSpringBootAdmin.git
15+
cd PythonClientForSpringBootAdmin
16+
pip install -r requirements.txt
17+
18+
```
19+
20+
### Setup your Service
21+
22+
Copy the template configuration file [app.conf](./app.conf) to create a custom configuration file (e.g., `cp app.conf app.local.conf`).
23+
24+
Edit your custom configuration file (e.g., `app.local.conf`) and define your environment values. Thereafter, the file might look like this:
25+
```ini
26+
[ServiceConfiguration]
27+
springbootadminserverurl = http://localhost:8080/
28+
springbootadminserveruser = admin
29+
springbootadminserverpassword = admin
30+
servicename = myname
31+
serviceport = 5000
32+
servicehost = http://127.0.0.1
33+
servicedescription = my component is doing some magic
34+
```
35+
36+
### Run the Service
37+
38+
A typical start of the service using the previously defined custom configuration `app.local.conf` might be done via:
39+
```sh
40+
python3 app.py -c app.local.conf
41+
```
42+
Now, check your Spring Boot Admin server UI. There should be a component visible having the name you had defined in your configuration file.
43+
44+
45+
### Test the Service
46+
47+
The service template already provides some endpoints. For example, a GET endpoint is available at the route `/`.
48+
If you have defined you run your service at `http://127.0.0.1` (servicehost) and port `5000` (serviceport), then you can open http://127.0.0.1:5000/ using your browser. The response will be `Hello, World!` as defined in [myservice.py](./myservice.py).
49+
50+
Additionally, a basic HTML file is provided using the data defined in the configuration file. Following the previously mentioned examplary configuration it would be available at: http://127.0.0.1:5000/about
51+
52+
53+
54+
## Customize your Service
55+
56+
### Project Structure
57+
58+
The following files are contained in the project.
59+
60+
```
61+
.
62+
├── app.conf
63+
├── app.py
64+
├── configuration.py
65+
├── myservice.py
66+
├── registration.py
67+
├── registrator.py
68+
├── requirements.txt
69+
└── templates
70+
└── about.html
71+
```
72+
Typically, only the file `myservice.py` will be customized.
73+
74+
### Implement your own Service Functionality
75+
76+
Implementing a customized service is possible using the Flask functionality. See the [Flash documentation](https://palletsprojects.com/p/flask/) for details.
77+
78+
79+
80+
## Features
81+
82+
The template service provides the following functionality:
83+
* calls the Spring Boot Admin server iteratively (by default: every 10 seconds)
84+
* some meta data is send to the server, too
85+
* provides a health interface availabe at `/health` which will be called by Spring Boot Admin server (callback)
86+
* the endpoint is used by the Spring Boot Admin server to check if the component is still available
87+
* provides an HTML page (available at `/about`) containing information about the component (see [templates/about.html](templates/about.html))
88+
* the presented data is taking from the (custom) config file
89+
90+
## Contributing
91+
92+
You might:
93+
* create a pull request (c.f., https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request)
94+
* [create an issue](./issues/new) to let us know about problems or feature requests
95+
* ...

‎app.conf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[ServiceConfiguration]
2+
# the URL of the Spring Boot Admin server endpoint (e.g., http://localhost:8080)
3+
springbootadminserverurl =
4+
# Spring Boot Admin server credentials (by default: admin, admin)
5+
springbootadminserveruser =
6+
springbootadminserverpassword =
7+
# the name of your service (e.g., my service)
8+
servicename =
9+
# the port (integer) of your service (e.g., 5000)
10+
serviceport =
11+
# the host of your service (e.g., http://127.0.0.1)
12+
servicehost =
13+
# a description of your service functionality
14+
servicedescription =

‎app.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import logging
2+
import argparse
3+
from flask import Flask, render_template, jsonify, request
4+
from datetime import datetime
5+
6+
from registration import Registration
7+
from registrator import Registrator
8+
from configuration import Configuration
9+
10+
from myservice import myservice
11+
12+
13+
# default config file (use -c parameter on command line specify a custom config file)
14+
configfile = "app.conf"
15+
16+
# endpoint for Web page containing information about the service
17+
aboutendpoint = "/about"
18+
19+
# endpoint for health information of the service required for Spring Boot Admin server callback
20+
healthendpoint = "/health"
21+
22+
23+
# initialize Flask app and add the externalized service information
24+
app = Flask(__name__)
25+
app.register_blueprint(myservice)
26+
27+
# holds the configuration
28+
configuration = None
29+
30+
31+
@app.route(healthendpoint, methods=['GET'])
32+
def health():
33+
"""required health endpoint for callback of Spring Boot Admin server"""
34+
return "alive"
35+
36+
@app.route(aboutendpoint)
37+
def about():
38+
"""optional endpoint for serving a web page with information about the web service"""
39+
return render_template("about.html", configuration=configuration)
40+
41+
42+
if __name__ == "__main__":
43+
logging.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)
44+
45+
# allow configuration of the configfile via command line parameters
46+
argparser = argparse.ArgumentParser(description='You might provide a configuration file, otherwise "%s" is used.' % (configfile) )
47+
argparser.add_argument('-c', '--configfile', action='store', dest='configfile', default=configfile, help='overwrite the default configfile "%s"' % (configfile))
48+
configfile = argparser.parse_args().configfile
49+
configuration = Configuration(configfile, ['springbootadminserverurl', 'springbootadminserveruser', 'springbootadminserverpassword', 'servicehost', 'serviceport', 'servicename', 'servicedescription'])
50+
try:
51+
configuration.serviceport = int(configuration.serviceport) # ensure an int value for the server port
52+
except Exception as e:
53+
logging.error("in configfile '%s': serviceport '%s' is not valid (%s)" % (configfile, configuration.serviceport, e) )
54+
55+
# define metadata that will be shown in the Spring Boot Admin server UI
56+
metadata = {
57+
"start": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
58+
"description": configuration.servicedescription,
59+
"about": "%s:%d%s" % (configuration.servicehost, configuration.serviceport, aboutendpoint),
60+
"written in": "Python"
61+
}
62+
63+
# initialize the registation object, to be send to the Spring Boot Admin server
64+
myRegistration = Registration(
65+
name=configuration.servicename,
66+
healthUrl="%s:%d%s" % (configuration.servicehost, configuration.serviceport, healthendpoint),
67+
metadata=metadata
68+
)
69+
70+
# start a thread that will contact iteratively the Spring Boot Admin server
71+
registratorThread = Registrator(
72+
configuration.springbootadminserverurl,
73+
configuration.springbootadminserveruser,
74+
configuration.springbootadminserverpassword,
75+
myRegistration
76+
)
77+
registratorThread.start()
78+
79+
# start the web service
80+
app.run(debug=True, port=configuration.serviceport)

‎configuration.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import configparser
2+
import os.path
3+
import logging
4+
5+
6+
class Configuration:
7+
"""
8+
parse and validates information provided by
9+
"""
10+
11+
demandedConfigurationKeys = []
12+
configfile = ""
13+
14+
def __init__(self, configfile, demandedConfigurationKeys=[]):
15+
self.configfile = configfile
16+
if os.path.exists(self.configfile):
17+
self.demandedConfigurationKeys = demandedConfigurationKeys
18+
self.integrateDataFromConfigfile()
19+
else:
20+
logging.error("config file '%s' not found" % (configfile))
21+
raise Exception(
22+
"no configuration present in file '%s'" % (configfile))
23+
24+
def integrateDataFromConfigfile(self) -> None:
25+
config = configparser.ConfigParser()
26+
config.read(self.configfile)
27+
configvalues = dict(config.items("ServiceConfiguration"))
28+
29+
# set all configuration values as object properties
30+
for key in configvalues:
31+
setattr(self, key, configvalues[key])
32+
if configvalues[key]:
33+
try:
34+
self.demandedConfigurationKeys.remove(key)
35+
except:
36+
pass
37+
logging.debug("configuration: %s=%s" % (key, configvalues[key]))
38+
39+
# test if all mandatory configuration values are present
40+
if(len(self.demandedConfigurationKeys) != 0):
41+
raise Exception("The following values are missing in config file '%s': %s" % (
42+
self.configfile, self.demandedConfigurationKeys))

‎myservice.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from flask import Blueprint, Flask, render_template, jsonify, request
2+
3+
myservice = Blueprint('myservice', __name__, template_folder='templates')
4+
5+
"""
6+
simple endpoints to show the external definition of a custom service
7+
"""
8+
9+
10+
@myservice.route("/", methods=['GET'])
11+
def index():
12+
"""an examplary GET endpoint returning "hello world2 (String)"""
13+
print(request.host_url)
14+
return "Hello, World!"
15+
16+
17+
@myservice.route("/", methods=['POST'])
18+
def indexJson():
19+
"""a POST endpoint returning a hello world JSON"""
20+
data = {'Hello': 'World'}
21+
return jsonify(data)

‎registration.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
class Registration:
3+
"""
4+
a similar implementation of the corresponding Spring Boot Admin class
5+
c.f,. https://github.com/codecentric/spring-boot-admin/blob/master/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Registration.java
6+
"""
7+
8+
name = None
9+
managementUrl = None
10+
healthUrl = None
11+
serviceUrl = None
12+
source = None
13+
metadata = {}
14+
15+
def __init__(self, name, healthUrl, source=None, managementUrl=None, serviceUrl=None, metadata={}):
16+
self.name = name
17+
self.managementUrl = managementUrl
18+
self.healthUrl = healthUrl
19+
self.serviceUrl = serviceUrl
20+
self.source = source
21+
self.metadata = metadata

‎registrator.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import threading
2+
import time
3+
import requests
4+
import json
5+
import logging
6+
from requests.auth import AuthBase, HTTPBasicAuth
7+
8+
9+
class Registrator(threading.Thread):
10+
"""
11+
class running as thread to contact the Spring Boot Admin Server
12+
"""
13+
14+
jsonHeaders = {"Content-type": "application/json",
15+
"Accept": "application/json"}
16+
adminServerURL = None
17+
adminServerUser = None
18+
adminServerPasswordregistration = None
19+
registration = None # passed dict containing relevant information
20+
interval = None # in seconds
21+
22+
def __init__(self, adminServerURL, adminServerUser, adminServerPassword, registration, interval=10):
23+
threading.Thread.__init__(self)
24+
self.adminServerURL = adminServerURL + "/instances"
25+
self.adminServerUser = adminServerUser
26+
self.adminServerPassword = adminServerPassword
27+
self.registration = registration
28+
self.interval = interval
29+
logging.basicConfig(level=logging.DEBUG)
30+
31+
def run(self):
32+
while True:
33+
self.callAdminServer()
34+
time.sleep(self.interval)
35+
36+
def callAdminServer(self):
37+
try:
38+
# prepare POST request data (None values should not been send)
39+
requestData = {k: v for k, v in vars(
40+
self.registration).items() if v is not None}
41+
response = requests.post(url=self.adminServerURL, headers=self.jsonHeaders, data=json.dumps(
42+
requestData), auth=HTTPBasicAuth(self.adminServerUser, self.adminServerPassword))
43+
44+
if response:
45+
logging.debug("registration: ok on %s (%d)" %
46+
(self.adminServerURL, response.status_code))
47+
else:
48+
logging.warning("registration: failed at %s with HTTP status code %d" % (
49+
self.adminServerURL, response.status_code))
50+
51+
except Exception as e:
52+
logging.warning("registration: failed at %s with exception \"%s\"" % (
53+
self.adminServerURL, e))

‎requirements.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# pip install -r requirements.txt
2+
# pip install --user -r requirements.txt
3+
Flask
4+
pprint
5+
requests
6+
configparser
7+
argparse
8+
waitress

‎templates/about.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!doctype html>
2+
3+
<html lang="en">
4+
<head>
5+
<meta charset="utf-8">
6+
7+
<title>About: {{configuration.servicename}}</title>
8+
<meta name="description" content="SHORT DESCRITION OF YOUR PROJECT">
9+
<meta name="author" content="YOUR NAME">
10+
11+
</head>
12+
13+
<body>
14+
<h1>{{configuration.servicename}}</h1>
15+
<p>{{configuration.servicedescription}}</p>
16+
</body>
17+
</html>

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /