forked from Hithomelabs/CFTunnels
203 lines
8.3 KiB
Java
203 lines
8.3 KiB
Java
package com.hithomelabs.CFTunnels.Services;
|
|
|
|
import com.hithomelabs.CFTunnels.Config.CloudflareConfig;
|
|
import com.hithomelabs.CFTunnels.Entity.Tunnel;
|
|
import com.hithomelabs.CFTunnels.Exceptions.ExternalServiceException;
|
|
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.Models.TunnelResult;
|
|
import com.hithomelabs.CFTunnels.Models.TunnelsResponse;
|
|
import com.hithomelabs.CFTunnels.Repositories.TunnelRepository;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.dao.DataAccessException;
|
|
import org.springframework.http.*;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.web.client.RestTemplate;
|
|
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.NoSuchElementException;
|
|
import java.util.Optional;
|
|
import java.util.UUID;
|
|
|
|
/**
|
|
* Service for interacting with the Cloudflare Tunnel API.
|
|
*
|
|
* <p>This service provides methods to manage Cloudflare Tunnels,
|
|
* including fetching tunnel configurations, creating/updating tunnels,
|
|
* and adding ingress mappings. It handles all communication with the
|
|
* Cloudflare API using the configured credentials.</p>
|
|
*
|
|
* <p><b>API Endpoints Used:</b></p>
|
|
* <ul>
|
|
* <li>GET /accounts/{accountId}/cfd_tunnel - List all tunnels</li>
|
|
* <li>GET /accounts/{accountId}/cfd_tunnel/{tunnelId}/configurations - Get tunnel config</li>
|
|
* <li>PUT /accounts/{accountId}/cfd_tunnel/{tunnelId}/configurations - Update tunnel config</li>
|
|
* </ul>
|
|
*
|
|
* @see CloudflareConfig
|
|
* @see Tunnel
|
|
* @see Ingress
|
|
*/
|
|
@Service
|
|
public class CloudflareAPIService {
|
|
|
|
/**
|
|
* Configuration for Cloudflare API credentials and settings.
|
|
* Loaded from application.properties using the {@code cloudflare.*} prefix.
|
|
*/
|
|
@Autowired
|
|
CloudflareConfig cloudflareConfig;
|
|
|
|
/**
|
|
* Header provider for Cloudflare API authentication.
|
|
* Generates the X-Auth-Key and X-Auth-Email headers.
|
|
*/
|
|
@Autowired
|
|
AuthKeyEmailHeader authKeyEmailHeader;
|
|
|
|
/**
|
|
* HTTP client for making API requests.
|
|
*/
|
|
@Autowired
|
|
RestTemplate restTemplate;
|
|
|
|
/**
|
|
* Repository for storing tunnel configurations locally.
|
|
*/
|
|
@Autowired
|
|
TunnelRepository tunnelRepository;
|
|
|
|
/**
|
|
* Fetches all Cloudflare tunnels from the API.
|
|
*
|
|
* @return Response containing the list of tunnels from Cloudflare
|
|
* @see TunnelsResponse
|
|
*/
|
|
public ResponseEntity<TunnelsResponse> getCloudflareTunnels() {
|
|
|
|
String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel";
|
|
|
|
HttpEntity<String> httpEntity = new HttpEntity<>("", authKeyEmailHeader.getHttpHeaders());
|
|
ResponseEntity<TunnelsResponse> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, TunnelsResponse.class);
|
|
return responseEntity;
|
|
}
|
|
|
|
/**
|
|
* Fetches the configuration for a specific tunnel.
|
|
*
|
|
* @param tunnelId The Cloudflare tunnel ID (UUID string)
|
|
* @param restTemplate HTTP client to use for the request
|
|
* @param responseType Class to deserialize the response into
|
|
* @param <T> Response type
|
|
* @return Response containing the tunnel configuration
|
|
*/
|
|
public <T> ResponseEntity<T> getCloudflareTunnelConfigurations(String tunnelId, RestTemplate restTemplate, Class<T> responseType) {
|
|
|
|
// Resource URL to hit get request at
|
|
String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel/" + tunnelId + "/configurations";
|
|
|
|
HttpEntity<String> httpEntity = new HttpEntity<>("",authKeyEmailHeader.getHttpHeaders());
|
|
ResponseEntity<T> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, responseType);
|
|
return responseEntity;
|
|
}
|
|
|
|
/**
|
|
* Updates the configuration for a specific tunnel.
|
|
*
|
|
* <p>This method is used to add, modify, or remove ingress mappings
|
|
* by providing a complete configuration object.</p>
|
|
*
|
|
* @param tunnelId The Cloudflare tunnel ID (UUID string)
|
|
* @param restTemplate HTTP client to use for the request
|
|
* @param responseType Class to deserialize the response into
|
|
* @param config The new configuration to apply
|
|
* @param <T> Response type
|
|
* @return Response containing the updated tunnel configuration
|
|
*/
|
|
public <T> ResponseEntity<T> putCloudflareTunnelConfigurations(String tunnelId, RestTemplate restTemplate, Class<T> responseType, Config config) {
|
|
|
|
// Resource URL to hit get request at
|
|
String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel/" + tunnelId + "/configurations";
|
|
|
|
HttpHeaders httpHeaders = authKeyEmailHeader.getHttpHeaders();
|
|
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
|
|
HttpEntity<Config> entity = new HttpEntity<>(config, httpHeaders);
|
|
ResponseEntity<T> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, entity, responseType);
|
|
return responseEntity;
|
|
}
|
|
|
|
/**
|
|
* Converts a Cloudflare API tunnel result to a local Tunnel entity.
|
|
*
|
|
* @param tunnelResult The tunnel result from Cloudflare API
|
|
* @param env The environment name to associate
|
|
* @return New Tunnel entity instance
|
|
*/
|
|
private Tunnel getTunnelFromTunnelResponse(TunnelResult tunnelResult, String env){
|
|
return new Tunnel(UUID.fromString(tunnelResult.getId()), env, tunnelResult.getName());
|
|
}
|
|
|
|
/**
|
|
* Creates or updates a tunnel's local configuration.
|
|
*
|
|
* <p>This method fetches the tunnel from Cloudflare API, validates it exists,
|
|
* and stores a local copy with the environment association.</p>
|
|
*
|
|
* @param tunnelId The Cloudflare tunnel ID
|
|
* @param environment Environment name (e.g., "production", "staging")
|
|
* @return The created or updated Tunnel entity
|
|
* @throws ExternalServiceException if Cloudflare API returns an error
|
|
* @throws NoSuchElementException if the tunnel doesn't exist in Cloudflare
|
|
*/
|
|
public Tunnel createOrUpdateTunnel(String tunnelId, String environment) throws ExternalServiceException, NoSuchElementException {
|
|
|
|
ResponseEntity<TunnelsResponse> responseEntity = getCloudflareTunnels();
|
|
|
|
if (responseEntity.getStatusCode().isError())
|
|
throw new ExternalServiceException("Cloudflare API error: " + responseEntity.getStatusCode());
|
|
|
|
TunnelResult tunnelResult = responseEntity.getBody().getResult().stream().filter(t -> t.getId().equals(tunnelId)).findFirst().orElse(null);
|
|
if (tunnelResult == null)
|
|
throw new NoSuchElementException();
|
|
|
|
Tunnel toBeConfigured = getTunnelFromTunnelResponse(tunnelResult, environment);
|
|
Tunnel fromDatabase = tunnelRepository.findById(UUID.fromString(tunnelId)).orElse(null);
|
|
|
|
if (fromDatabase != null)
|
|
tunnelRepository.deleteById(UUID.fromString(tunnelId));
|
|
tunnelRepository.save(toBeConfigured);
|
|
return toBeConfigured;
|
|
}
|
|
|
|
/**
|
|
* Retrieves all tunnels that have been locally configured.
|
|
*
|
|
* @return List of locally configured tunnels
|
|
*/
|
|
public List<Tunnel> getAllConfiguredTunnels() {
|
|
return tunnelRepository.findAll();
|
|
}
|
|
|
|
/**
|
|
* Adds an ingress mapping to an existing tunnel.
|
|
*
|
|
* <p>The new ingress is inserted at the second-to-last position in the
|
|
* ingress list (Cloudflare requires a catch-all rule at the end).</p>
|
|
*
|
|
* @param tunnelId The Cloudflare tunnel ID
|
|
* @param ingress The ingress configuration to add
|
|
* @return Response with the updated tunnel configuration
|
|
*/
|
|
public ResponseEntity<TunnelResponse> addTunnelIngress(String tunnelId, Ingress ingress) {
|
|
ResponseEntity<TunnelResponse> currentConfig = getCloudflareTunnelConfigurations(tunnelId, restTemplate, TunnelResponse.class);
|
|
|
|
Config config = currentConfig.getBody().getResult().getConfig();
|
|
List<Ingress> ingressList = config.getIngress();
|
|
ingressList.add(ingressList.size() - 1, ingress);
|
|
|
|
return putCloudflareTunnelConfigurations(tunnelId, restTemplate, TunnelResponse.class, config);
|
|
}
|
|
} |