forked from Hithomelabs/CFTunnels
merged with main branch
This commit is contained in:
commit
e2b2cc3ead
26
.gitea/workflows/integration_test.yaml
Normal file
26
.gitea/workflows/integration_test.yaml
Normal file
@ -0,0 +1,26 @@
|
||||
name: Daily cloudflare API integration test
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '0 */12 * * *' # Every hour
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
cloudflare-api-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: JDK setup
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
- name: Run integration tests with Cloudflare API
|
||||
env:
|
||||
SPRING_PROFILES_ACTIVE: integration
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
|
||||
CLOUDFLARE_EMAIL: hitanshu98@gmail.com
|
||||
run: ./gradlew integrationTestOnly
|
||||
@ -1,5 +1,5 @@
|
||||
name: sample gradle build and test
|
||||
run-name: Build started by $ {{gitea.actor}}
|
||||
run-name: Build started by ${{ gitea.actor }}
|
||||
on:
|
||||
push:
|
||||
branches: [test]
|
||||
@ -13,17 +13,19 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get new version
|
||||
id: new_version
|
||||
run: |
|
||||
VERSION=$(git describe --tags --abbrev=0)
|
||||
echo ${VERSION}
|
||||
echo "Current version: ${VERSION}"
|
||||
MAJOR=$(echo ${VERSION} | cut -d "." -f 1)
|
||||
MINOR=$(echo ${VERSION} | cut -d "." -f 2)
|
||||
PATCH=$(echo ${VERSION} | cut -d "." -f 3)
|
||||
NEW_PATCH=$(( ${PATCH} + 1))
|
||||
echo ${NEW_PATCH}
|
||||
echo "new_version=$(echo "${MAJOR}.${MINOR}.${NEW_PATCH}")" >> $GITHUB_OUTPUT
|
||||
NEW_PATCH=$((PATCH + 1))
|
||||
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
|
||||
echo "New version: ${NEW_VERSION}"
|
||||
echo "new_version=${NEW_VERSION}" >> $GITHUB_OUTPUT
|
||||
build_tag_push:
|
||||
runs-on: ubuntu-latest
|
||||
needs: tag
|
||||
@ -43,11 +45,11 @@ jobs:
|
||||
uses: gradle/actions/wrapper-validation@v3
|
||||
- name: Create and push tag
|
||||
run: |
|
||||
echo "NEW_VERSION=${{ needs.tag.outputs.new_version }}"
|
||||
git config --global user.name "${{gitea.actor}}"
|
||||
echo "New version: ${{ needs.tag.outputs.new_version }}"
|
||||
git config --global user.name "${{ gitea.actor }}"
|
||||
git config --global user.email "${{ gitea.actor }}@users.noreply.github.com"
|
||||
git tag -a ${{ needs.tag.outputs.new_version }} -m "Pushing new version ${{ needs.tag.outputs.new_version }}"
|
||||
git push origin ${{ needs.tag.outputs.new_version }}
|
||||
git tag -a "${{ needs.tag.outputs.new_version }}" -m "Pushing new version ${{ needs.tag.outputs.new_version }}"
|
||||
git push origin "${{ needs.tag.outputs.new_version }}"
|
||||
- name: Log in to Gitea Docker Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@ -62,3 +64,23 @@ jobs:
|
||||
run: |
|
||||
docker push 192.168.0.100:8928/hithomelabs/cftunnels:test
|
||||
docker push 192.168.0.100:8928/hithomelabs/cftunnels:${{ needs.tag.outputs.new_version }}
|
||||
sync_forks:
|
||||
name: Sync All Forks
|
||||
runs-on: ubuntu-latest
|
||||
needs: build_tag_push
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: Sync all forks via Gitea API
|
||||
run: |
|
||||
echo "Fetching forks for Hithomelabs/CFTunnels..."
|
||||
response=$(curl -s -X GET "https://gitea.hithomelabs.com/api/v1/repos/Hithomelabs/CFTunnels/forks" -H "Authorization: token ${{secrets.TOKEN}}")
|
||||
filtered=$(echo "$response" | grep -o '"clone_url":"[^"]*"' | sed 's/"clone_url":"\([^"]*\)"/\1/' | grep -v "/Hithomelabs")
|
||||
echo "Detected forks:"
|
||||
echo "$filtered"
|
||||
readarray -t forks <<< "$filtered"
|
||||
for fork_url in "${forks[@]}"; do
|
||||
echo "🔄 Syncing fork: $fork_url"
|
||||
authed_url=$(echo "$fork_url" | sed "s#https://#https://${{secrets.TOKEN}}@#")
|
||||
git push "$authed_url" test &
|
||||
done
|
||||
|
||||
21
build.gradle
21
build.gradle
@ -15,6 +15,27 @@ java {
|
||||
|
||||
test {
|
||||
systemProperty 'spring.profiles.active', 'test'
|
||||
useJUnitPlatform {
|
||||
excludeTags 'integration'
|
||||
}
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
exceptionFormat "full" // shows full stack trace
|
||||
showStandardStreams = true // shows println/log output
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('integrationTestOnly', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'integration'
|
||||
}
|
||||
description = 'Runs only integration tests tagged with @Tag("integration")'
|
||||
group = 'verification'
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed"
|
||||
exceptionFormat "full"
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
@ -1 +0,0 @@
|
||||
rootProject.name = 'CFTunnels'
|
||||
@ -1,7 +1,5 @@
|
||||
package com.hithomelabs.CFTunnels.Config;
|
||||
|
||||
import com.hithomelabs.CFTunnels.Entity.User;
|
||||
import com.hithomelabs.CFTunnels.Repositories.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
@ -5,10 +5,12 @@ import io.swagger.v3.oas.models.servers.Server;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@Configuration
|
||||
@Profile("!integration")
|
||||
public class OpenApiConfig {
|
||||
|
||||
@Value("${api.baseUrl}")
|
||||
|
||||
@ -5,7 +5,6 @@ import com.hithomelabs.CFTunnels.Config.AuthoritiesToGroupMapping;
|
||||
import com.hithomelabs.CFTunnels.Config.CloudflareConfig;
|
||||
import com.hithomelabs.CFTunnels.Config.RestTemplateConfig;
|
||||
import com.hithomelabs.CFTunnels.Entity.Request;
|
||||
import com.hithomelabs.CFTunnels.Entity.User;
|
||||
import com.hithomelabs.CFTunnels.Headers.AuthKeyEmailHeader;
|
||||
import com.hithomelabs.CFTunnels.Models.Config;
|
||||
import com.hithomelabs.CFTunnels.Models.Ingress;
|
||||
@ -19,12 +18,13 @@ import org.springframework.http.*;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/cloudflare")
|
||||
@ -142,7 +142,7 @@ public class TunnelController implements ErrorController {
|
||||
return ResponseEntity.ok(jsonResponse);
|
||||
}
|
||||
|
||||
// @PreAuthorize("hasAnyRole('DEVELOPER')")
|
||||
@PreAuthorize("hasAnyRole('DEVELOPER')")
|
||||
@PutMapping("/tunnel/{tunnelId}/request")
|
||||
public ResponseEntity<Request> createTunnelMappingRequest(@PathVariable String tunnelId, @AuthenticationPrincipal OidcUser oidcUser, @RequestBody Ingress ingess){
|
||||
Request request = mappingRequestService.createMappingRequest(tunnelId, ingess, oidcUser);
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
package com.hithomelabs.CFTunnels.Models;
|
||||
|
||||
import java.net.URI;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class Ingress {
|
||||
|
||||
private String service;
|
||||
@ -11,40 +19,8 @@ public class Ingress {
|
||||
private Map<String, Object> originRequest;
|
||||
private String path;
|
||||
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public static boolean deleteByHostName(List<Ingress> ingressList, String toBeDeleted){
|
||||
return ingressList.removeIf(ingress -> ingress.getHostname() != null && ingress.getHostname().equals(toBeDeleted));
|
||||
}
|
||||
|
||||
public String getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
public void setService(String service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public void setHostname(String hostname) {
|
||||
this.hostname = hostname;
|
||||
}
|
||||
|
||||
public Map<String, Object> getOriginRequest() {
|
||||
return originRequest;
|
||||
}
|
||||
|
||||
public void setOriginRequest(Map<String, Object> originRequest) {
|
||||
this.originRequest = originRequest;
|
||||
}
|
||||
}
|
||||
|
||||
3
src/main/resources/application-integration.properties
Normal file
3
src/main/resources/application-integration.properties
Normal file
@ -0,0 +1,3 @@
|
||||
cloudflare.accountId=${CLOUDFLARE_ACCOUNT_ID}
|
||||
cloudflare.apiKey=${CLOUDFLARE_API_KEY}
|
||||
cloudflare.email=${CLOUDFLARE_EMAIL}
|
||||
@ -4,12 +4,6 @@ cloudflare.apiKey=${CLOUDFLARE_API_KEY}
|
||||
cloudflare.email=${CLOUDFLARE_EMAIL}
|
||||
spring.profiles.active=${ENV}
|
||||
|
||||
# set root level
|
||||
logging.level.root=INFO
|
||||
# package-specific
|
||||
logging.level.org.springframework=TRACE
|
||||
logging.level.com.myapp=INFO
|
||||
|
||||
/ * * Masking sure app works behind a reverse proxy
|
||||
server.forward-headers-strategy=framework
|
||||
|
||||
|
||||
@ -0,0 +1,107 @@
|
||||
package com.hithomelabs.CFTunnels.Integration;
|
||||
|
||||
import com.hithomelabs.CFTunnels.Config.CloudflareConfig;
|
||||
import com.hithomelabs.CFTunnels.Headers.AuthKeyEmailHeader;
|
||||
import com.hithomelabs.CFTunnels.Models.Config;
|
||||
import com.hithomelabs.CFTunnels.Models.Ingress;
|
||||
import com.hithomelabs.CFTunnels.Models.TunnelResponse;
|
||||
import com.hithomelabs.CFTunnels.Services.CloudflareAPIService;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("integration")
|
||||
@Tag("integration")
|
||||
public class CoudflareApiIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
RestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
AuthKeyEmailHeader authKeyEmailHeader;
|
||||
|
||||
@Autowired
|
||||
CloudflareConfig cloudflareConfig;
|
||||
|
||||
@Autowired
|
||||
CloudflareAPIService cloudflareAPIService;
|
||||
|
||||
private static final String DEV_TUNNEL_ID = "50df9101-f625-4618-b7c5-100338a57124";
|
||||
|
||||
@Test
|
||||
@DisplayName("Calls cloudflare cfd tunnels API and checks that dev tunnel should be a part of the response")
|
||||
public void testGetTunnelsTest() {
|
||||
|
||||
ResponseEntity<Map> response = cloudflareAPIService.getCloudflareTunnels();
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
|
||||
List<Map<String, Object>> tunnelList = (List<Map<String, Object>>) response.getBody().get("result");
|
||||
boolean hasName = tunnelList.stream()
|
||||
.anyMatch(tunnel -> "devtunnel".equals(tunnel.get("name")));
|
||||
|
||||
assertTrue(hasName);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Calls cloudflare API to get mappings for devtunnel tunnel")
|
||||
public void testTunnelConfigurations() {
|
||||
|
||||
ResponseEntity<TunnelResponse> responseEntity = cloudflareAPIService.getCloudflareTunnelConfigurations(DEV_TUNNEL_ID, restTemplate, TunnelResponse.class);
|
||||
|
||||
// * * Check if status code is 200
|
||||
assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
|
||||
|
||||
// * * Checking if mapping for devdocker exists
|
||||
TunnelResponse tunnelResponse = responseEntity.getBody();
|
||||
boolean hasMatch = tunnelResponse.getResult().getConfig().getIngress().stream()
|
||||
.anyMatch(ingress -> "devdocker.hithomelabs.com".equals(ingress.getHostname()));
|
||||
assertTrue(hasMatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Inserts and deletes a mapping using Cloudflare API")
|
||||
public void testAddAndDeleteMapping() {
|
||||
|
||||
ResponseEntity<TunnelResponse> beforeMapping = cloudflareAPIService.getCloudflareTunnelConfigurations(DEV_TUNNEL_ID, restTemplate, TunnelResponse.class);
|
||||
|
||||
assertEquals(HttpStatus.OK, beforeMapping.getStatusCode());
|
||||
|
||||
Ingress ingress = new Ingress();
|
||||
ingress.setHostname("random.hithomelabs.com");
|
||||
ingress.setService("http://192.168.0.100:3457");
|
||||
|
||||
Config beforeInsertConfig = beforeMapping.getBody().getResult().getConfig();
|
||||
List<Ingress> beforeInsert = beforeInsertConfig.getIngress();
|
||||
beforeInsert.add(beforeInsert.size() - 1, ingress);
|
||||
|
||||
ResponseEntity<TunnelResponse> afterInsert = cloudflareAPIService.putCloudflareTunnelConfigurations(DEV_TUNNEL_ID, restTemplate, TunnelResponse.class, beforeInsertConfig);
|
||||
|
||||
assertEquals(HttpStatus.OK, afterInsert.getStatusCode());
|
||||
Config afterInsertConfig = afterInsert.getBody().getResult().getConfig();
|
||||
List<Ingress> ingressList = afterInsertConfig.getIngress();
|
||||
|
||||
boolean hasIngress = ingressList.get(ingressList.size() - 2 ).getHostname().equals("random.hithomelabs.com");
|
||||
assertTrue(hasIngress);
|
||||
|
||||
Boolean deleteSuccess = Ingress.deleteByHostName(ingressList, ingress.getHostname());
|
||||
assertTrue(deleteSuccess);
|
||||
|
||||
ResponseEntity<TunnelResponse> afterDelete = cloudflareAPIService.putCloudflareTunnelConfigurations(DEV_TUNNEL_ID, restTemplate, TunnelResponse.class, afterInsertConfig);
|
||||
assertEquals(HttpStatus.OK, afterDelete.getStatusCode());
|
||||
assertFalse(afterDelete.getBody().getResult().getConfig().getIngress().stream().anyMatch(anyIngress -> "random.hithomelabs.com".equals(anyIngress.getHostname())));
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user