Skip to content

Commit a478a38

Browse files
Merge branch 'release/0.54.0' into main
2 parents 91dcafa + 68b19a0 commit a478a38

File tree

193 files changed

+11974
-8156
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

193 files changed

+11974
-8156
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
We love every form of contribution. By participating in this project, you
44
agree to abide to the `Airy` [code of conduct](/code_of_conduct.md).
55

6-
Please read our [contributing guide](/docs/docs/guides/contributing.md) to
6+
Please read our [contributing guide](/docs/docs/guides/contributing-to-airy.md) to
77
learn how to develop with the `Airy Core Platform` and what conventions we
88
follow.
99

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ real-time and historical data to wherever you need it.
6969

7070
Airy Core comes with all the components you need to stream historical and real-time data.
7171

72-
- 💬 Pre-built and easily configurable [connectors](https://airy.co/docs/core/sources/introduction)
72+
- 💬 Pre-built and easily configurable [connectors](https://airy.co/docs/core/connectors/sources/introduction)
7373

7474
By ingesting all real-time events and continuously processing, aggregating and joining them in the stream, development time can be significantly reduced. Through integrations with pre-built and easily configured connectors, events are consumed from any source, including business systems such as ERP/CRM, conversational sources, third party APIs. Airy also comes with an SDK to build custom connectors to any source.
7575

@@ -88,7 +88,7 @@ clients to receive near real-time updates about data flowing through the system.
8888
A webhook integration server that allows its users to create actionable workflows (the webhook integration
8989
exposes events users can "listen" to and react programmatically.)
9090

91-
- 💎[UI: From a control center to dashboards](https://airy.co/docs/core/apps/ui/introduction)
91+
- 💎[UI](https://airy.co/docs/core/ui/overview) to access the data and the control center through a browser
9292

9393
No-code interfaces to manage and control Airy, your connectors and your streams.
9494

@@ -97,7 +97,7 @@ No-code interfaces to manage and control Airy, your connectors and your streams.
9797
We welcome (and love) every form of contribution! Good entry points to the
9898
project are:
9999

100-
- Our [contributing guide](/docs/docs/guides/contributing.md)
100+
- Our [contributing guide](/docs/docs/guides/contributing-to-airy.md)
101101
- Issues with the tag
102102
[gardening](https://github.com/airyhq/airy/issues?q=is%3Aissue+is%3Aopen+label%3Agardening)
103103
- Issues with the tag [good first

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.53.0
1+
0.54.0

backend/components/admin/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ app_deps = [
2525
"//lib/java/spring/kafka/core:spring-kafka-core",
2626
"//lib/java/spring/kafka/streams:spring-kafka-streams",
2727
"//lib/java/tracking:tracking",
28+
"@maven//:org_springframework_retry_spring_retry",
2829
]
2930

3031
springboot(
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package co.airy.core.admin;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.http.HttpEntity;
5+
import org.springframework.http.HttpHeaders;
6+
import org.springframework.http.HttpMethod;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.retry.annotation.Retryable;
9+
import org.springframework.web.bind.annotation.RequestBody;
10+
import org.springframework.web.bind.annotation.RequestMapping;
11+
import org.springframework.web.bind.annotation.RestController;
12+
import org.springframework.web.client.HttpStatusCodeException;
13+
import org.springframework.web.client.RestTemplate;
14+
import org.springframework.web.util.UriComponentsBuilder;
15+
16+
import javax.servlet.http.HttpServletRequest;
17+
import java.net.URI;
18+
import java.util.Enumeration;
19+
20+
@RestController
21+
public class RegistryProxy {
22+
private final String upstreamHost;
23+
private final int upstreamPort;
24+
private final RestTemplate restTemplate;
25+
26+
public RegistryProxy(@Value("${KAFKA_REST_UPSTREAM_HOST}") String upstreamHost,
27+
@Value("${KAFKA_REST_UPSTREAM_PORT:80}") int upstreamPort, RestTemplate restTemplate) {
28+
this.upstreamHost = upstreamHost;
29+
this.upstreamPort = upstreamPort;
30+
this.restTemplate = restTemplate;
31+
}
32+
33+
34+
@RequestMapping("/kafka/**")
35+
public ResponseEntity<?> proxyRequest(@RequestBody(required = false) String body, HttpMethod method, HttpServletRequest request) {
36+
return executeRequest(body, method, request);
37+
}
38+
39+
@Retryable(exclude = {
40+
HttpStatusCodeException.class}, include = Exception.class, maxAttempts = 3)
41+
public ResponseEntity<?> executeRequest(String body,
42+
HttpMethod method, HttpServletRequest request) {
43+
String requestUrl = request.getRequestURI();
44+
45+
URI uri = UriComponentsBuilder.newInstance()
46+
.scheme("http")
47+
.host(upstreamHost)
48+
.port(upstreamPort)
49+
.path(requestUrl.replace("/kafka", ""))
50+
.query(request.getQueryString())
51+
.build(true).toUri();
52+
53+
HttpHeaders headers = new HttpHeaders();
54+
Enumeration<String> headerNames = request.getHeaderNames();
55+
56+
while (headerNames.hasMoreElements()) {
57+
String headerName = headerNames.nextElement();
58+
headers.set(headerName, request.getHeader(headerName));
59+
}
60+
61+
HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
62+
try {
63+
return restTemplate.exchange(uri, method, httpEntity, String.class);
64+
} catch (HttpStatusCodeException e) {
65+
return ResponseEntity.status(e.getRawStatusCode())
66+
.headers(e.getResponseHeaders())
67+
.body(e.getResponseBodyAsString());
68+
}
69+
}
70+
}

backend/components/admin/src/test/resources/test.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ kafka.cleanup=true
22
kafka.commit-interval-ms=100
33
kubernetes.namespace=default
44
kubernetes.app=api-admin
5+
KAFKA_REST_UPSTREAM_HOST=schema-registry
6+
KAFKA_REST_UPSTREAM_PORT=8082

backend/components/streams/BUILD

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
load("@com_github_airyhq_bazel_tools//lint:buildifier.bzl", "check_pkg")
2+
3+
# gazelle:prefix github.com/airyhq/airy/backend/components/streams
4+
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
5+
load("@io_bazel_rules_docker//go:image.bzl", "go_image")
6+
load("//tools/build:container_release.bzl", "container_release")
7+
8+
go_library(
9+
name = "streams_lib",
10+
srcs = [
11+
"auth.go",
12+
"cors.go",
13+
"main.go",
14+
"streams_create.go",
15+
"streams_delete.go",
16+
"streams_info.go",
17+
"streams_list.go",
18+
],
19+
importpath = "github.com/airyhq/airy/backend/components/streams",
20+
visibility = ["//visibility:private"],
21+
deps = [
22+
"//lib/go/httpclient",
23+
"//lib/go/payloads",
24+
"@com_github_golang_jwt_jwt//:jwt",
25+
"@com_github_gorilla_mux//:mux",
26+
"@io_k8s_klog//:klog",
27+
],
28+
)
29+
30+
go_binary(
31+
name = "streams",
32+
out = "streams",
33+
embed = [":streams_lib"],
34+
visibility = ["//visibility:public"],
35+
)
36+
37+
genrule(
38+
name = "streams_bin_rule",
39+
srcs = [":streams"],
40+
outs = ["streams_bin"],
41+
cmd = "cp $(SRCS) $@",
42+
)
43+
44+
go_image(
45+
name = "image",
46+
embed = [":streams_lib"],
47+
)
48+
49+
container_release(
50+
registry = "ghcr.io/airyhq/api/components",
51+
repository = "streams",
52+
)
53+
54+
check_pkg(name = "buildifier")

backend/components/streams/auth.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/base64"
6+
"log"
7+
"net/http"
8+
"regexp"
9+
"strings"
10+
11+
"github.com/golang-jwt/jwt"
12+
"k8s.io/klog"
13+
)
14+
15+
type EnableAuthMiddleware struct {
16+
pattern *regexp.Regexp
17+
}
18+
19+
// MustNewAuthMiddleware Only paths that match the regexp pattern will be authenticated
20+
func MustNewAuthMiddleware(pattern string) EnableAuthMiddleware {
21+
r, err := regexp.Compile(pattern)
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
return EnableAuthMiddleware{pattern: r}
26+
}
27+
28+
func (a EnableAuthMiddleware) Middleware(next http.Handler) http.Handler {
29+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
30+
ctx := r.Context()
31+
if !a.pattern.MatchString(r.URL.Path) {
32+
next.ServeHTTP(w, r)
33+
return
34+
}
35+
36+
// Auth middlewares attach a flag to the context indicating that authentication was successful
37+
if val, ok := ctx.Value("auth").(bool); ok && val {
38+
next.ServeHTTP(w, r)
39+
} else {
40+
http.Error(w, "Forbidden", http.StatusForbidden)
41+
}
42+
})
43+
}
44+
45+
type SystemTokenMiddleware struct {
46+
systemToken string
47+
}
48+
49+
func NewSystemTokenMiddleware(systemToken string) SystemTokenMiddleware {
50+
return SystemTokenMiddleware{systemToken: systemToken}
51+
}
52+
53+
func (s SystemTokenMiddleware) Middleware(next http.Handler) http.Handler {
54+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
55+
authPayload := r.Header.Get("Authorization")
56+
authPayload = strings.TrimPrefix(authPayload, "Bearer ")
57+
58+
if authPayload == s.systemToken {
59+
ctx := context.WithValue(r.Context(), "auth", true)
60+
next.ServeHTTP(w, r.WithContext(ctx))
61+
return
62+
}
63+
next.ServeHTTP(w, r)
64+
})
65+
}
66+
67+
type JwtMiddleware struct {
68+
jwtSecret []byte
69+
}
70+
71+
func NewJwtMiddleware(jwtSecret string) JwtMiddleware {
72+
data, err := base64.StdEncoding.DecodeString(jwtSecret)
73+
if err != nil {
74+
klog.Fatal("failed to base64 decode jwt secret: ", err)
75+
}
76+
77+
return JwtMiddleware{jwtSecret: data}
78+
}
79+
80+
func (j JwtMiddleware) Middleware(next http.Handler) http.Handler {
81+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
82+
authPayload := r.Header.Get("Authorization")
83+
authPayload = strings.TrimPrefix(authPayload, "Bearer ")
84+
if authPayload == "" {
85+
authPayload = getAuthCookie(r)
86+
}
87+
88+
token, err := jwt.Parse(authPayload, func(token *jwt.Token) (interface{}, error) {
89+
return j.jwtSecret, nil
90+
})
91+
92+
if err != nil || !token.Valid {
93+
next.ServeHTTP(w, r)
94+
return
95+
}
96+
97+
ctx := context.WithValue(r.Context(), "auth", true)
98+
next.ServeHTTP(w, r.WithContext(ctx))
99+
})
100+
}
101+
102+
func getAuthCookie(r *http.Request) string {
103+
cookie, err := r.Cookie("airy_auth_token")
104+
if err != nil {
105+
return ""
106+
}
107+
return cookie.Value
108+
}

backend/components/streams/cors.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package main
2+
3+
import (
4+
"net/http"
5+
"strings"
6+
)
7+
8+
type CORS struct {
9+
allowedOrigins map[string]struct{}
10+
}
11+
12+
func NewCORSMiddleware(allowedOrigins string) CORS {
13+
cors := CORS{allowedOrigins: make(map[string]struct{})}
14+
15+
for _, origin := range strings.Split(allowedOrigins, ",") {
16+
cors.allowedOrigins[origin] = struct{}{}
17+
}
18+
19+
return cors
20+
}
21+
22+
func (c *CORS) Middleware(next http.Handler) http.Handler {
23+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24+
25+
origin := r.Header.Get("Origin")
26+
_, allowed := c.allowedOrigins[origin]
27+
28+
if !allowed && r.Method == "OPTIONS" {
29+
w.WriteHeader(http.StatusForbidden)
30+
return
31+
}
32+
33+
if allowed {
34+
w.Header().Set("Access-Control-Allow-Credentials", "true")
35+
w.Header().Set("Access-Control-Allow-Origin", origin)
36+
w.Header().Set("Access-Control-Allow-Methods", "POST, GET")
37+
w.Header().Set(
38+
"Access-Control-Allow-Headers",
39+
"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Requested-With, X-XSRF-Token",
40+
)
41+
}
42+
43+
if r.Method == "OPTIONS" {
44+
return
45+
}
46+
47+
next.ServeHTTP(w, r)
48+
})
49+
}

backend/components/streams/helm/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
load("//tools/build:helm.bzl", "helm_ruleset_core_version")
2+
3+
helm_ruleset_core_version()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
apiVersion: v2
2+
appVersion: "1.0"
3+
description: A Helm chart for the streams backend
4+
name: api-streams
5+
version: 0.1
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: "{{ .Values.name }}"
5+
labels:
6+
core.airy.co/managed: "true"
7+
core.airy.co/mandatory: "{{ .Values.mandatory }}"
8+
core.airy.co/component: "{{ .Values.name }}"
9+
annotations:
10+
core.airy.co/enabled: "{{ .Values.enabled }}"

0 commit comments

Comments
 (0)