Compare commits
No commits in common. "3c47643cd49b3c5d27bfffb9fb476b2d0f8f199d" and "6fc44a6969645898db9c32814ff35c84651aadee" have entirely different histories.
3c47643cd4
...
6fc44a6969
@ -5,7 +5,6 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
implementation 'org.apache.httpcomponents.client5:httpclient5'
|
|
||||||
|
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
annotationProcessor 'org.projectlombok:lombok'
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
|||||||
@ -2,12 +2,8 @@ package com.hithomelabs.portainer;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
||||||
|
|
||||||
import com.hithomelabs.portainer.config.PortainerAutomationProperties;
|
@SpringBootApplication
|
||||||
|
|
||||||
@SpringBootApplication(scanBasePackages = {"com.hithomelabs.portainer", "com.hithomelabs.common"})
|
|
||||||
@EnableConfigurationProperties(PortainerAutomationProperties.class)
|
|
||||||
public class PortainerAutomationApplication {
|
public class PortainerAutomationApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
package com.hithomelabs.portainer.config;
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
|
|
||||||
@ConfigurationProperties(prefix = "portainer")
|
|
||||||
public class PortainerAutomationProperties {
|
|
||||||
private String baseUrl;
|
|
||||||
private String apiKey;
|
|
||||||
private Long endpointId;
|
|
||||||
private Service service = new Service();
|
|
||||||
|
|
||||||
public String getBaseUrl() {
|
|
||||||
return baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBaseUrl(String baseUrl) {
|
|
||||||
this.baseUrl = baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getApiKey() {
|
|
||||||
return apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setApiKey(String apiKey) {
|
|
||||||
this.apiKey = apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getEndpointId() {
|
|
||||||
return endpointId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEndpointId(Long endpointId) {
|
|
||||||
this.endpointId = endpointId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Service getService() {
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setService(Service service) {
|
|
||||||
this.service = service;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Service {
|
|
||||||
private String apiKey;
|
|
||||||
|
|
||||||
public String getApiKey() {
|
|
||||||
return apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setApiKey(String apiKey) {
|
|
||||||
this.apiKey = apiKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
package com.hithomelabs.portainer.config;
|
|
||||||
|
|
||||||
import org.apache.hc.client5.http.impl.classic.HttpClients;
|
|
||||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
|
|
||||||
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
|
|
||||||
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
|
|
||||||
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
|
|
||||||
import org.apache.hc.core5.ssl.SSLContexts;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import com.hithomelabs.common.portainer.client.PortainerApiClient;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class PortainerClientConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public RestTemplate portainerRestTemplate() throws Exception {
|
|
||||||
SSLContext sslContext = SSLContexts.custom()
|
|
||||||
.loadTrustMaterial((chain, authType) -> true)
|
|
||||||
.build();
|
|
||||||
SSLConnectionSocketFactory sslSocketFactory =
|
|
||||||
new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
|
|
||||||
HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
|
|
||||||
.setSSLSocketFactory(sslSocketFactory)
|
|
||||||
.build();
|
|
||||||
return new RestTemplate(new HttpComponentsClientHttpRequestFactory(
|
|
||||||
HttpClients.custom().setConnectionManager(cm).build()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public PortainerApiClient portainerApiClient(PortainerAutomationProperties props,
|
|
||||||
RestTemplate portainerRestTemplate) {
|
|
||||||
PortainerApiClient client = new PortainerApiClient(portainerRestTemplate, props.getBaseUrl());
|
|
||||||
client.authenticateWithApiKey(props.getApiKey());
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
package com.hithomelabs.portainer.controller;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import com.hithomelabs.portainer.config.PortainerAutomationProperties;
|
|
||||||
import com.hithomelabs.portainer.service.DeployService;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/deploy")
|
|
||||||
public class DeployController {
|
|
||||||
|
|
||||||
private final DeployService deployService;
|
|
||||||
private final PortainerAutomationProperties props;
|
|
||||||
|
|
||||||
public DeployController(DeployService deployService, PortainerAutomationProperties props) {
|
|
||||||
this.deployService = deployService;
|
|
||||||
this.props = props;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/{stackId}")
|
|
||||||
public ResponseEntity<String> deploy(@PathVariable Long stackId,
|
|
||||||
@RequestHeader(value = "X-API-Key", required = false) String apiKey) {
|
|
||||||
String expectedApiKey = props.getService().getApiKey();
|
|
||||||
if (expectedApiKey == null || !expectedApiKey.equals(apiKey)) {
|
|
||||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid API key");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
deployService.redeploy(stackId);
|
|
||||||
return ResponseEntity.ok("Deployment initiated for stack " + stackId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
||||||
.body("Deployment failed: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
package com.hithomelabs.portainer.service;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import com.hithomelabs.common.portainer.client.PortainerApiClient;
|
|
||||||
import com.hithomelabs.portainer.config.PortainerAutomationProperties;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class DeployService {
|
|
||||||
|
|
||||||
private final PortainerApiClient portainerApiClient;
|
|
||||||
private final PortainerAutomationProperties props;
|
|
||||||
|
|
||||||
public DeployService(PortainerApiClient portainerApiClient,
|
|
||||||
PortainerAutomationProperties props) {
|
|
||||||
this.portainerApiClient = portainerApiClient;
|
|
||||||
this.props = props;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void redeploy(Long stackId) {
|
|
||||||
portainerApiClient.redeployGitStack(stackId, props.getEndpointId(), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +1 @@
|
|||||||
server.port=8081
|
server.port=8081
|
||||||
portainer.base-url=https://192.168.0.100:9442
|
|
||||||
portainer.endpoint-id=2
|
|
||||||
portainer.service.api-key=change-me
|
|
||||||
|
|||||||
@ -1,73 +0,0 @@
|
|||||||
package com.hithomelabs.portainer.controller;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.anyLong;
|
|
||||||
import static org.mockito.Mockito.doThrow;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
|
||||||
|
|
||||||
import com.hithomelabs.portainer.config.PortainerAutomationProperties;
|
|
||||||
import com.hithomelabs.portainer.service.DeployService;
|
|
||||||
|
|
||||||
@WebMvcTest(DeployController.class)
|
|
||||||
class DeployControllerTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private MockMvc mockMvc;
|
|
||||||
|
|
||||||
@MockitoBean
|
|
||||||
private DeployService deployService;
|
|
||||||
|
|
||||||
@MockitoBean
|
|
||||||
private PortainerAutomationProperties props;
|
|
||||||
|
|
||||||
@MockitoBean
|
|
||||||
private PortainerAutomationProperties.Service service;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
when(props.getService()).thenReturn(service);
|
|
||||||
when(service.getApiKey()).thenReturn("valid-key");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void deployWithValidKeyReturnsOk() throws Exception {
|
|
||||||
mockMvc.perform(post("/api/deploy/1")
|
|
||||||
.header("X-API-Key", "valid-key"))
|
|
||||||
.andExpect(status().isOk())
|
|
||||||
.andExpect(content().string("Deployment initiated for stack 1"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void deployWithInvalidKeyReturnsUnauthorized() throws Exception {
|
|
||||||
mockMvc.perform(post("/api/deploy/1")
|
|
||||||
.header("X-API-Key", "wrong-key"))
|
|
||||||
.andExpect(status().isUnauthorized())
|
|
||||||
.andExpect(content().string("Invalid API key"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void deployWithMissingKeyReturnsUnauthorized() throws Exception {
|
|
||||||
mockMvc.perform(post("/api/deploy/1"))
|
|
||||||
.andExpect(status().isUnauthorized())
|
|
||||||
.andExpect(content().string("Invalid API key"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void deployWhenServiceFailsReturnsServerError() throws Exception {
|
|
||||||
doThrow(new RuntimeException("Portainer error"))
|
|
||||||
.when(deployService).redeploy(anyLong());
|
|
||||||
mockMvc.perform(post("/api/deploy/1")
|
|
||||||
.header("X-API-Key", "valid-key"))
|
|
||||||
.andExpect(status().isInternalServerError())
|
|
||||||
.andExpect(content().string("Deployment failed: Portainer error"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user