mirror of https://github.com/portainer/portainer
				
				
				
			
		
			
				
	
	
		
			406 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			406 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
package endpointedge
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
	"net/http/httptest"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	portainer "github.com/portainer/portainer/api"
 | 
						|
	"github.com/portainer/portainer/api/apikey"
 | 
						|
	"github.com/portainer/portainer/api/chisel"
 | 
						|
	"github.com/portainer/portainer/api/datastore"
 | 
						|
	"github.com/portainer/portainer/api/filesystem"
 | 
						|
	"github.com/portainer/portainer/api/http/security"
 | 
						|
	"github.com/portainer/portainer/api/jwt"
 | 
						|
 | 
						|
	"github.com/segmentio/encoding/json"
 | 
						|
	"github.com/stretchr/testify/assert"
 | 
						|
	"github.com/stretchr/testify/require"
 | 
						|
)
 | 
						|
 | 
						|
type endpointTestCase struct {
 | 
						|
	endpoint           portainer.Endpoint
 | 
						|
	endpointRelation   portainer.EndpointRelation
 | 
						|
	expectedStatusCode int
 | 
						|
}
 | 
						|
 | 
						|
var endpointTestCases = []endpointTestCase{
 | 
						|
	{
 | 
						|
		portainer.Endpoint{},
 | 
						|
		portainer.EndpointRelation{},
 | 
						|
		http.StatusForbidden,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		portainer.Endpoint{
 | 
						|
			ID:     -1,
 | 
						|
			Name:   "endpoint-id--1",
 | 
						|
			Type:   portainer.EdgeAgentOnDockerEnvironment,
 | 
						|
			URL:    "https://portainer.io:9443",
 | 
						|
			EdgeID: "edge-id",
 | 
						|
		},
 | 
						|
		portainer.EndpointRelation{
 | 
						|
			EndpointID: -1,
 | 
						|
		},
 | 
						|
		http.StatusForbidden,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		portainer.Endpoint{
 | 
						|
			ID:     2,
 | 
						|
			Name:   "endpoint-id-2",
 | 
						|
			Type:   portainer.EdgeAgentOnDockerEnvironment,
 | 
						|
			URL:    "https://portainer.io:9443",
 | 
						|
			EdgeID: "",
 | 
						|
		},
 | 
						|
		portainer.EndpointRelation{
 | 
						|
			EndpointID: 2,
 | 
						|
		},
 | 
						|
		http.StatusForbidden,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		portainer.Endpoint{
 | 
						|
			ID:     4,
 | 
						|
			Name:   "endpoint-id-4",
 | 
						|
			Type:   portainer.EdgeAgentOnDockerEnvironment,
 | 
						|
			URL:    "https://portainer.io:9443",
 | 
						|
			EdgeID: "edge-id",
 | 
						|
		},
 | 
						|
		portainer.EndpointRelation{
 | 
						|
			EndpointID: 4,
 | 
						|
		},
 | 
						|
		http.StatusOK,
 | 
						|
	},
 | 
						|
}
 | 
						|
 | 
						|
func mustSetupHandler(t *testing.T) *Handler {
 | 
						|
	tmpDir := t.TempDir()
 | 
						|
	fs, err := filesystem.NewService(tmpDir, "")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("could not start a new filesystem service: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	_, store := datastore.MustNewTestStore(t, true, true)
 | 
						|
 | 
						|
	ctx := context.Background()
 | 
						|
	shutdownCtx, cancelFn := context.WithCancel(ctx)
 | 
						|
	t.Cleanup(cancelFn)
 | 
						|
 | 
						|
	jwtService, err := jwt.NewService("1h", store)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("could not start a new JWT service: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	apiKeyService := apikey.NewAPIKeyService(nil, nil)
 | 
						|
 | 
						|
	settings, err := store.Settings().Settings()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("could not create new settings: %s", err)
 | 
						|
	}
 | 
						|
	settings.TrustOnFirstConnect = true
 | 
						|
 | 
						|
	if err = store.Settings().UpdateSettings(settings); err != nil {
 | 
						|
		t.Fatalf("could not update settings: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	handler := NewHandler(
 | 
						|
		security.NewRequestBouncer(store, jwtService, apiKeyService),
 | 
						|
		store,
 | 
						|
		fs,
 | 
						|
		chisel.NewService(store, shutdownCtx, nil),
 | 
						|
	)
 | 
						|
 | 
						|
	handler.ReverseTunnelService = chisel.NewService(store, shutdownCtx, nil)
 | 
						|
 | 
						|
	return handler
 | 
						|
}
 | 
						|
 | 
						|
func createEndpoint(handler *Handler, endpoint portainer.Endpoint, endpointRelation portainer.EndpointRelation) (err error) {
 | 
						|
	// Avoid setting ID below 0 to generate invalid test cases
 | 
						|
	if endpoint.ID <= 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if err := handler.DataStore.Endpoint().Create(&endpoint); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return handler.DataStore.EndpointRelation().Create(&endpointRelation)
 | 
						|
}
 | 
						|
 | 
						|
func TestMissingEdgeIdentifier(t *testing.T) {
 | 
						|
	handler := mustSetupHandler(t)
 | 
						|
	endpointID := portainer.EndpointID(45)
 | 
						|
 | 
						|
	if err := createEndpoint(handler, portainer.Endpoint{
 | 
						|
		ID:     endpointID,
 | 
						|
		Name:   "endpoint-id-45",
 | 
						|
		Type:   portainer.EdgeAgentOnDockerEnvironment,
 | 
						|
		URL:    "https://portainer.io:9443",
 | 
						|
		EdgeID: "edge-id",
 | 
						|
	}, portainer.EndpointRelation{EndpointID: endpointID}); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpointID), nil)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("request error:", err)
 | 
						|
	}
 | 
						|
 | 
						|
	rec := httptest.NewRecorder()
 | 
						|
	handler.ServeHTTP(rec, req)
 | 
						|
 | 
						|
	if rec.Code != http.StatusForbidden {
 | 
						|
		t.Fatalf("expected a %d response, found: %d without Edge identifier", http.StatusForbidden, rec.Code)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestWithEndpoints(t *testing.T) {
 | 
						|
	handler := mustSetupHandler(t)
 | 
						|
 | 
						|
	for _, test := range endpointTestCases {
 | 
						|
		err := createEndpoint(handler, test.endpoint, test.endpointRelation)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
 | 
						|
		req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", test.endpoint.ID), nil)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal("request error:", err)
 | 
						|
		}
 | 
						|
 | 
						|
		req.Header.Set(portainer.PortainerAgentEdgeIDHeader, test.endpoint.EdgeID)
 | 
						|
		req.Header.Set(portainer.HTTPResponseAgentPlatform, "1")
 | 
						|
 | 
						|
		rec := httptest.NewRecorder()
 | 
						|
		handler.ServeHTTP(rec, req)
 | 
						|
 | 
						|
		if rec.Code != test.expectedStatusCode {
 | 
						|
			t.Fatalf("expected a %d response, found: %d for endpoint ID: %d", test.expectedStatusCode, rec.Code, test.endpoint.ID)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestLastCheckInDateIncreases(t *testing.T) {
 | 
						|
	handler := mustSetupHandler(t)
 | 
						|
 | 
						|
	endpointID := portainer.EndpointID(56)
 | 
						|
	endpoint := portainer.Endpoint{
 | 
						|
		ID:              endpointID,
 | 
						|
		Name:            "test-endpoint-56",
 | 
						|
		Type:            portainer.EdgeAgentOnDockerEnvironment,
 | 
						|
		URL:             "https://portainer.io:9443",
 | 
						|
		EdgeID:          "edge-id",
 | 
						|
		LastCheckInDate: time.Now().Unix(),
 | 
						|
	}
 | 
						|
 | 
						|
	endpointRelation := portainer.EndpointRelation{
 | 
						|
		EndpointID: endpoint.ID,
 | 
						|
	}
 | 
						|
 | 
						|
	if err := createEndpoint(handler, endpoint, endpointRelation); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	time.Sleep(1 * time.Second)
 | 
						|
 | 
						|
	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("request error:", err)
 | 
						|
	}
 | 
						|
 | 
						|
	req.Header.Set(portainer.PortainerAgentEdgeIDHeader, "edge-id")
 | 
						|
	req.Header.Set(portainer.HTTPResponseAgentPlatform, "1")
 | 
						|
 | 
						|
	rec := httptest.NewRecorder()
 | 
						|
	handler.ServeHTTP(rec, req)
 | 
						|
 | 
						|
	if rec.Code != http.StatusOK {
 | 
						|
		t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
 | 
						|
	}
 | 
						|
 | 
						|
	updatedEndpoint, err := handler.DataStore.Endpoint().Endpoint(endpoint.ID)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	assert.Greater(t, updatedEndpoint.LastCheckInDate, endpoint.LastCheckInDate)
 | 
						|
}
 | 
						|
 | 
						|
func TestEmptyEdgeIdWithAgentPlatformHeader(t *testing.T) {
 | 
						|
	handler := mustSetupHandler(t)
 | 
						|
 | 
						|
	endpointID := portainer.EndpointID(44)
 | 
						|
	edgeId := "edge-id"
 | 
						|
	endpoint := portainer.Endpoint{
 | 
						|
		ID:     endpointID,
 | 
						|
		Name:   "test-endpoint-44",
 | 
						|
		Type:   portainer.EdgeAgentOnDockerEnvironment,
 | 
						|
		URL:    "https://portainer.io:9443",
 | 
						|
		EdgeID: "",
 | 
						|
	}
 | 
						|
	endpointRelation := portainer.EndpointRelation{
 | 
						|
		EndpointID: endpoint.ID,
 | 
						|
	}
 | 
						|
 | 
						|
	if err := createEndpoint(handler, endpoint, endpointRelation); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("request error:", err)
 | 
						|
	}
 | 
						|
 | 
						|
	req.Header.Set(portainer.PortainerAgentEdgeIDHeader, edgeId)
 | 
						|
	req.Header.Set(portainer.HTTPResponseAgentPlatform, "1")
 | 
						|
 | 
						|
	rec := httptest.NewRecorder()
 | 
						|
	handler.ServeHTTP(rec, req)
 | 
						|
 | 
						|
	if rec.Code != http.StatusOK {
 | 
						|
		t.Fatalf("expected a %d response, found: %d with empty edge ID", http.StatusOK, rec.Code)
 | 
						|
	}
 | 
						|
 | 
						|
	updatedEndpoint, err := handler.DataStore.Endpoint().Endpoint(endpoint.ID)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	assert.Equal(t, updatedEndpoint.EdgeID, edgeId)
 | 
						|
}
 | 
						|
 | 
						|
func TestEdgeStackStatus(t *testing.T) {
 | 
						|
	handler := mustSetupHandler(t)
 | 
						|
 | 
						|
	endpointID := portainer.EndpointID(7)
 | 
						|
	endpoint := portainer.Endpoint{
 | 
						|
		ID:              endpointID,
 | 
						|
		Name:            "test-endpoint-7",
 | 
						|
		Type:            portainer.EdgeAgentOnDockerEnvironment,
 | 
						|
		URL:             "https://portainer.io:9443",
 | 
						|
		EdgeID:          "edge-id",
 | 
						|
		LastCheckInDate: time.Now().Unix(),
 | 
						|
	}
 | 
						|
 | 
						|
	edgeStackID := portainer.EdgeStackID(17)
 | 
						|
	edgeStack := portainer.EdgeStack{
 | 
						|
		ID:   edgeStackID,
 | 
						|
		Name: "test-edge-stack-17",
 | 
						|
		Status: map[portainer.EndpointID]portainer.EdgeStackStatus{
 | 
						|
			endpointID: {},
 | 
						|
		},
 | 
						|
		CreationDate:   time.Now().Unix(),
 | 
						|
		EdgeGroups:     []portainer.EdgeGroupID{1, 2},
 | 
						|
		ProjectPath:    "/project/path",
 | 
						|
		EntryPoint:     "entrypoint",
 | 
						|
		Version:        237,
 | 
						|
		ManifestPath:   "/manifest/path",
 | 
						|
		DeploymentType: 1,
 | 
						|
	}
 | 
						|
 | 
						|
	endpointRelation := portainer.EndpointRelation{
 | 
						|
		EndpointID: endpoint.ID,
 | 
						|
		EdgeStacks: map[portainer.EdgeStackID]bool{
 | 
						|
			edgeStack.ID: true,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	err := handler.DataStore.EdgeStack().Create(edgeStack.ID, &edgeStack)
 | 
						|
	require.NoError(t, err)
 | 
						|
 | 
						|
	if err := createEndpoint(handler, endpoint, endpointRelation); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("request error:", err)
 | 
						|
	}
 | 
						|
 | 
						|
	req.Header.Set(portainer.PortainerAgentEdgeIDHeader, "edge-id")
 | 
						|
	req.Header.Set(portainer.HTTPResponseAgentPlatform, "1")
 | 
						|
 | 
						|
	rec := httptest.NewRecorder()
 | 
						|
	handler.ServeHTTP(rec, req)
 | 
						|
 | 
						|
	if rec.Code != http.StatusOK {
 | 
						|
		t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
 | 
						|
	}
 | 
						|
 | 
						|
	var data endpointEdgeStatusInspectResponse
 | 
						|
	if err := json.NewDecoder(rec.Body).Decode(&data); err != nil {
 | 
						|
		t.Fatal("error decoding response:", err)
 | 
						|
	}
 | 
						|
 | 
						|
	assert.Len(t, data.Stacks, 1)
 | 
						|
	assert.Equal(t, edgeStack.ID, data.Stacks[0].ID)
 | 
						|
	assert.Equal(t, edgeStack.Version, data.Stacks[0].Version)
 | 
						|
}
 | 
						|
 | 
						|
func TestEdgeJobsResponse(t *testing.T) {
 | 
						|
	handler := mustSetupHandler(t)
 | 
						|
 | 
						|
	endpointID := portainer.EndpointID(77)
 | 
						|
	endpoint := portainer.Endpoint{
 | 
						|
		ID:              endpointID,
 | 
						|
		Name:            "test-endpoint-77",
 | 
						|
		Type:            portainer.EdgeAgentOnDockerEnvironment,
 | 
						|
		URL:             "https://portainer.io:9443",
 | 
						|
		EdgeID:          "edge-id",
 | 
						|
		LastCheckInDate: time.Now().Unix(),
 | 
						|
	}
 | 
						|
 | 
						|
	endpointRelation := portainer.EndpointRelation{
 | 
						|
		EndpointID: endpoint.ID,
 | 
						|
	}
 | 
						|
 | 
						|
	if err := createEndpoint(handler, endpoint, endpointRelation); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	path, err := handler.FileService.StoreEdgeJobFileFromBytes("test-script", []byte("pwd"))
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	edgeJobID := portainer.EdgeJobID(35)
 | 
						|
	edgeJob := portainer.EdgeJob{
 | 
						|
		ID:             edgeJobID,
 | 
						|
		Created:        time.Now().Unix(),
 | 
						|
		CronExpression: "* * * * *",
 | 
						|
		Name:           "test-edge-job",
 | 
						|
		ScriptPath:     path,
 | 
						|
		Recurring:      true,
 | 
						|
		Version:        57,
 | 
						|
	}
 | 
						|
 | 
						|
	handler.ReverseTunnelService.AddEdgeJob(&endpoint, &edgeJob)
 | 
						|
 | 
						|
	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("request error:", err)
 | 
						|
	}
 | 
						|
 | 
						|
	req.Header.Set(portainer.PortainerAgentEdgeIDHeader, "edge-id")
 | 
						|
	req.Header.Set(portainer.HTTPResponseAgentPlatform, "1")
 | 
						|
 | 
						|
	rec := httptest.NewRecorder()
 | 
						|
	handler.ServeHTTP(rec, req)
 | 
						|
 | 
						|
	if rec.Code != http.StatusOK {
 | 
						|
		t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
 | 
						|
	}
 | 
						|
 | 
						|
	var data endpointEdgeStatusInspectResponse
 | 
						|
	if err := json.NewDecoder(rec.Body).Decode(&data); err != nil {
 | 
						|
		t.Fatal("error decoding response:", err)
 | 
						|
	}
 | 
						|
 | 
						|
	assert.Len(t, data.Schedules, 1)
 | 
						|
	assert.Equal(t, edgeJob.ID, data.Schedules[0].ID)
 | 
						|
	assert.Equal(t, edgeJob.CronExpression, data.Schedules[0].CronExpression)
 | 
						|
	assert.Equal(t, edgeJob.Version, data.Schedules[0].Version)
 | 
						|
}
 |