Skip to content

Commit 44caea9

Browse files
authored
Add Rayvens CLI (#57)
* Add Rayvens CLI. * Add support for modeline options. * Add kamel operations file. * Make built integration image runnable with integration file mount. * Create and delete Kubernetes deployment. * Add support for deploying integrations as kubernetes deployments. * Add support for integrations with extra files. * Enable job launching capabilities. * Pass event content to job. * Factor out files operations. * Rename file. Add java preloader file. * Refactor docker and file system interactions. * Refactor integration building. * Refactor run method. * Kubernetes entity deployment. Enable toggling of job launching. * Enable job launching for all source types. * Enable integration updates when deploying. * Enable local communication with sink. * Add verbose flag to run command. Output endpoint for sinks. * Make local integration launch asynchronous. Check for integration status. Check integration is ready. * Add name to generated containers. Delete and remove containers during clean-up. * Add examples. Add REDME. Allow full name images. * Update kamel version to 1.6.1. * Add instructions for running basic integration.
1 parent c00747f commit 44caea9

27 files changed

+3919
-83
lines changed

rayvens/cli/README.md

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<!--
2+
# Copyright IBM Corporation 2021
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
-->
16+
17+
# Rayvens CLI
18+
19+
Rayvens offers a command line interface for creating, launching and deleting integrations that receive or send events to external event sources and sinks respectively.
20+
21+
Requirements: Python 3.8+, Docker and Kubernetes.
22+
23+
## Integrations
24+
25+
Integrations are processes which interact with event sources and event sinks on behalf of the user. Integrations offer a uniform way to send/receive events between a user application and a diverse set of event sources and sinks.
26+
27+
The Rayvens CLI uses the `quay.io/rayvens` image registry to store intermediate images. Pre-built images exist of commonly used integrations such as `slack-sink`.
28+
29+
### Running a pre-built image locally:
30+
31+
To run the pre-built Slack sink integration run:
32+
33+
```
34+
rayvens run --image quay.io/rayvens/slack-sink -p channel=<...> webhook_url=<...>
35+
```
36+
37+
This will run the slack sink integration on the local machine as a Docker container. To view the created container perform a `docker ps`.
38+
39+
When the integration is ready to receive events, the command will output the endpoint on which it expects to receive events. For example:
40+
41+
```
42+
> rayvens run --image quay.io/rayvens/slack-sink -p channel=<...> webhook_url=<...>
43+
http://localhost:53844/my-integration-route
44+
```
45+
46+
To remove the integration run:
47+
48+
```
49+
rayvens delete --name slack-sink
50+
```
51+
52+
This will stop, kill and remove the docker container from the list of existing containers.
53+
54+
### Removing an integration with a custom name:
55+
56+
By default the name of the integration being run is the integration type which is also the image name i.e. `slack-sink`. The name of the application can be set by the user using the `--name` flag:
57+
58+
```
59+
rayvens run --image quay.io/rayvens/slack-sink -p channel=<...> webhook_url=<...> --name my-integration
60+
```
61+
62+
Deleting this integration will now become:
63+
64+
```
65+
rayvens delete --name my-integration
66+
```
67+
68+
The command will delete all the docker containers with a name starting with the string `my-container`.
69+
70+
### Running a pre-built image in a Kubernetes cluster:
71+
To run the pre-built Slack sink integration in a Kubernetes cluster add the `--deploy` flag to the previous command line:
72+
73+
```
74+
rayvens run --image quay.io/rayvens/slack-sink -p channel=<...> webhook_url=<...> --deploy
75+
```
76+
77+
To remove a Kubernetes-deployed integration with a default name run:
78+
79+
```
80+
rayvens delete --name slack-sink --deployed
81+
```
82+
83+
This command will remove any Kubernetes deployments and services.

rayvens/cli/__init__.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#
2+
# Copyright IBM Corporation 2021
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#

rayvens/cli/build.py

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#
2+
# Copyright IBM Corporation 2021
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
import os
18+
import yaml
19+
import rayvens.cli.utils as utils
20+
import rayvens.cli.file as file
21+
import rayvens.cli.java as java
22+
import rayvens.cli.docker as docker
23+
from rayvens.core.catalog import sources, sinks
24+
from rayvens.core.catalog import construct_source, construct_sink
25+
26+
27+
def build_base_image(args):
28+
# Create docker file for the base image.
29+
docker_image = docker.JavaAlpineDockerImage()
30+
31+
# Install packages into the image.
32+
docker_image.install("maven")
33+
docker_image.install("bash")
34+
docker_image.install("curl")
35+
docker_image.update_installed_packages()
36+
37+
# Bring in kamel executable:
38+
docker_image.add_kamel()
39+
40+
# TODO: remove this, overwrite kamel executable with one from host.
41+
path_to_local_kamel = file.find_executable("kamel-linux")
42+
kamel_executable = file.File(path_to_local_kamel)
43+
docker_image.copy(kamel_executable, "/usr/local/bin/kamel")
44+
45+
# Add kubernetes capabilities:
46+
docker_image.add_kubernetes()
47+
48+
# Create preloader file with string content type:
49+
preload_file = file.File(java.preloader_file_name,
50+
contents=java.preloader_file_contents)
51+
docker_image.copy(preload_file)
52+
53+
# Add run command:
54+
docker_image.run(f"""kamel local run {preload_file.name} \
55+
--dependency mvn:org.apache.camel.quarkus:camel-quarkus-java-joor-dsl \
56+
--dependency camel:camel-quarkus-microprofile-health; \
57+
rm {preload_file.name}""")
58+
59+
# Build image:
60+
docker_image.build(utils.get_base_image_name(args))
61+
62+
# Push base image to registry.
63+
docker_image.push()
64+
65+
66+
def build_integration(args):
67+
# Create image for integration:
68+
docker_image = docker.DockerImage(utils.get_base_image_name(args))
69+
70+
# Create workspace inside the image.
71+
docker_image.run(f"mkdir -p /{docker.image_workspace_name}")
72+
docker_image.workdir(f"/{docker.image_workspace_name}")
73+
74+
# Create the yaml source for the integration locally and transfer it
75+
# to the image.
76+
predefined_integration = args.kind is not None and (args.kind in sources
77+
or args.kind in sinks)
78+
inverted_transport = True
79+
launches_kubectl_jobs = args.launch_image is not None
80+
81+
# Put together the summary file.
82+
summary_file = utils.get_summary_file(args)
83+
84+
# The name of the integration:
85+
name = args.kind
86+
if args.name is not None:
87+
name = args.name
88+
89+
# The name of the integration file:
90+
integration_file_name = utils.get_kubernetes_integration_file_name(name)
91+
92+
if predefined_integration:
93+
# Get a skeleton configuration for this integration kind.
94+
base_config, _ = utils.get_current_config(args)
95+
96+
# Create the integration yaml specification.
97+
route = "/" + args.kind + "-route"
98+
if args.kind in sources:
99+
spec = construct_source(base_config,
100+
f'platform-http:{route}',
101+
inverted=inverted_transport)
102+
else:
103+
spec = construct_sink(base_config, f'platform-http:{route}')
104+
105+
# Any command line specified properties get transformed into modeline
106+
# options. Modeline options live at the top of the integration source
107+
# file.
108+
modeline_options = utils.get_modeline_config(args)
109+
integration_source_file = modeline_options + "\n\n" + yaml.dump(spec)
110+
integration_file = file.File(integration_file_name,
111+
contents=integration_source_file)
112+
docker_image.copy(integration_file)
113+
114+
# Check if additional files need to be added.
115+
additional_files = utils.get_additional_files(spec, inverted_transport,
116+
args.launch_image)
117+
for additional_file in additional_files:
118+
docker_image.copy(additional_file)
119+
120+
# Add summary file to image.
121+
docker_image.copy(summary_file)
122+
else:
123+
raise RuntimeError("Not implemented yet")
124+
125+
# Copy the current kubeconfig to the workspace directory:
126+
if launches_kubectl_jobs:
127+
path_to_kubeconfig = os.path.expanduser('~') + "/.kube/config"
128+
kubeconfig_file = file.File(path_to_kubeconfig)
129+
docker_image.copy(kubeconfig_file)
130+
131+
# Additional files to be added to the RUN command line:
132+
files_list = " ".join(
133+
[additional_file.name for additional_file in additional_files])
134+
135+
# Command that is run when building the image. This command is meant
136+
# to preload all dependencies to run the integration.
137+
run_command = f"""kamel local build {integration_file.name} {files_list} \
138+
--integration-directory my-integration \
139+
--dependency mvn:org.apache.camel.quarkus:camel-quarkus-java-joor-dsl \
140+
--dependency mvn:com.googlecode.json-simple:json-simple:1.1.1 \
141+
--dependency camel:camel-quarkus-microprofile-health"""
142+
if launches_kubectl_jobs:
143+
run_command += " --dependency mvn:io.kubernetes:client-java:11.0.0"
144+
docker_image.run(run_command)
145+
146+
# List of all environment variables either given on the command line
147+
# or as part of the summary file or are part of the inner image scope
148+
# and are relevant to kamel local run.
149+
envvars = utils.get_modeline_envvars(summary_file, args)
150+
if launches_kubectl_jobs:
151+
envvars.extend([
152+
"PATH", "KUBERNETES_SERVICE_PORT", "KUBERNETES_PORT", "HOSTNAME",
153+
"JAVA_VERSION", "KUBERNETES_PORT_443_TCP_ADDR",
154+
"KUBERNETES_PORT_443_TCP_PORT", "KUBERNETES_PORT_443_TCP_PROTO",
155+
"LANG", "HTTP_SOURCE_ENTRYPOINT_PORT", "KUBERNETES_PORT_443_TCP",
156+
"KUBERNETES_SERVICE_PORT_HTTPS", "LC_ALL", "JAVA_HOME",
157+
"KUBERNETES_SERVICE_HOST", "PWD"
158+
])
159+
160+
# The list of envvars is of the format:
161+
# --env ENV_VAR=$ENV_VAR
162+
outer_scope_envvars = []
163+
for envvar in envvars:
164+
outer_scope_envvars.append(f"--env {envvar}=${envvar}")
165+
outer_scope_envvars = " ".join(outer_scope_envvars)
166+
167+
# Kubernetes does not allow us to mount a file over an existing file.
168+
# This means that when we deploy the integration on Kubernetes, we need
169+
# to mount the updated integration file to a separate location in the image
170+
# and then copy the image in the correct location i.e. the routes subfolder
171+
# to overwrite the old integration yaml file.
172+
docker_image.cmd(
173+
f"{docker.update_integration_file_in_image(integration_file_name)}; "
174+
f"kamel local run --integration-directory "
175+
f"{docker.built_integration_directory} "
176+
f"{outer_scope_envvars}")
177+
178+
# Build image.
179+
docker_image.build(utils.get_integration_image(args))
180+
181+
# Push integration image to registry.
182+
docker_image.push()

0 commit comments

Comments
 (0)