Skip to content

Commit

Permalink
fail if the idpBuilder is launched on a different port
Browse files Browse the repository at this point in the history
fixes #159

Signed-off-by: Nima Kaviani <[email protected]>
  • Loading branch information
nimakaviani committed Feb 29, 2024
1 parent ea9c56e commit 016c309
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 3 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ require (
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
Expand Down
40 changes: 38 additions & 2 deletions pkg/docker/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
)

func GetOneContainer(ctx context.Context, dockerClient *client.Client, listOptions types.ContainerListOptions) (*types.Container, error) {
func GetOneContainer(ctx context.Context, dockerClient client.APIClient, listOptions types.ContainerListOptions) (*types.Container, error) {
gotContainers, err := dockerClient.ContainerList(ctx, listOptions)
if err != nil {
return nil, err
Expand All @@ -23,7 +23,43 @@ func GetOneContainer(ctx context.Context, dockerClient *client.Client, listOptio
return &gotContainers[0], nil
}

func Exec(ctx context.Context, dockerClient *client.Client, container string, config types.ExecConfig) error {
func GetContainerByName(ctx context.Context, name string, dockerClient client.APIClient, listOptions types.ContainerListOptions) (*types.Container, error) {
gotContainers, err := dockerClient.ContainerList(ctx, listOptions)
if err != nil {
return nil, err
}

if len(gotContainers) == 0 {
return nil, nil
}

var gotContainer *types.Container
for _, container := range gotContainers {
for _, containerName := range container.Names {
// internal docker container name includes a fwd slash in the name
if containerName == fmt.Sprintf("/%s", name) {
gotContainer = &container
break
}

if gotContainer != nil {
break
}
}
}
return gotContainer, nil
}

func IsUsingPort(container *types.Container, port uint16) bool {
for _, p := range container.Ports {
if p.PublicPort == port {
return true
}
}
return false
}

func Exec(ctx context.Context, dockerClient client.APIClient, container string, config types.ExecConfig) error {
log := log.FromContext(ctx)
resp, err := dockerClient.ContainerExecCreate(ctx, container, config)
if err != nil {
Expand Down
90 changes: 89 additions & 1 deletion pkg/kind/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@ package kind
import (
"context"
"embed"
"errors"
"fmt"
"io/fs"
"os"
"strconv"
"strings"

"github.com/cnoe-io/idpbuilder/pkg/docker"
"github.com/cnoe-io/idpbuilder/pkg/util"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"sigs.k8s.io/kind/pkg/cluster"
"sigs.k8s.io/kind/pkg/cluster/nodes"
"sigs.k8s.io/kind/pkg/cluster/nodeutils"
)

type Cluster struct {
provider *cluster.Provider
provider IProvider
name string
kubeVersion string
kubeConfigPath string
Expand All @@ -27,6 +34,15 @@ type PortMapping struct {
ContainerPort string
}

type IProvider interface {
List() ([]string, error)
ListNodes(string) ([]nodes.Node, error)
CollectLogs(string, string) error
Delete(string, string) error
Create(string, ...cluster.CreateOption) error
ExportKubeConfig(string, string, bool) error
}

//go:embed resources/kind.yaml
var configFS embed.FS

Expand Down Expand Up @@ -109,16 +125,73 @@ func (c *Cluster) Exists() (bool, error) {
return false, nil
}

func (c *Cluster) RunsOnRightPort(cli client.APIClient, ctx context.Context) (bool, error) {

allNodes, err := c.provider.ListNodes(c.name)
if err != nil {
return false, err
}

cpNodes, err := nodeutils.ControlPlaneNodes(allNodes)
if err != nil {
return false, err
}

var cpNodeName string
for _, cpNode := range cpNodes {
if strings.Contains(cpNode.String(), c.name) {
cpNodeName = cpNode.String()
}
}
if cpNodeName == "" {
return false, nil
}

container, err := docker.GetContainerByName(ctx, cpNodeName, cli, types.ContainerListOptions{})
if err != nil {
return false, err
}

if container == nil {
return false, nil
}

userPort, err := toUint16(c.cfg.Port)
if err != nil {
return false, err
}

return docker.IsUsingPort(container, userPort), nil
}

func (c *Cluster) Reconcile(ctx context.Context, recreate bool) error {
clusterExitsts, err := c.Exists()
if err != nil {
return err
}

if clusterExitsts {
if recreate {
fmt.Printf("Existing cluster %s found. Deleting.\n", c.name)
c.provider.Delete(c.name, "")
} else {
// check if user is requesting a different port
// for the idpBuilder
cli, err := docker.GetDockerClient()
if err != nil {
return err
}

rightPort, err := c.RunsOnRightPort(cli, ctx)
if err != nil {
return err
}

if !rightPort {
return fmt.Errorf("cant serve port %s. cluster %s is already running on a different port", c.cfg.Port, c.name)
}

// reuse if there is no port conflict
fmt.Printf("Cluster %s already exists\n", c.name)
return nil
}
Expand Down Expand Up @@ -149,3 +222,18 @@ func (c *Cluster) Reconcile(ctx context.Context, recreate bool) error {
func (c *Cluster) ExportKubeConfig(name string, internal bool) error {
return c.provider.ExportKubeConfig(name, c.kubeConfigPath, internal)
}

func toUint16(portString string) (uint16, error) {
// Convert port string to uint16
port, err := strconv.ParseUint(portString, 10, 16)
if err != nil {
return 0, err
}

// Port validation
if port > 65535 {
return 0, errors.New("Invalid port number")
}

return uint16(port), nil
}
122 changes: 122 additions & 0 deletions pkg/kind/cluster_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package kind

import (
"context"
"io"
"testing"

"github.com/cnoe-io/idpbuilder/pkg/util"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"sigs.k8s.io/kind/pkg/cluster/constants"
"sigs.k8s.io/kind/pkg/cluster/nodes"
"sigs.k8s.io/kind/pkg/exec"
)

func TestGetConfig(t *testing.T) {
Expand Down Expand Up @@ -73,3 +81,117 @@ nodes:

assert.Equal(t, expectConfig, string(cfg))
}

// Mock provider for testing
type mockProvider struct {
mock.Mock
IProvider
}

func (m *mockProvider) ListNodes(name string) ([]nodes.Node, error) {
args := m.Called(name)
return args.Get(0).([]nodes.Node), args.Error(1)
}

// Mock Docker client for testing
type DockerClientMock struct {
client.APIClient
mock.Mock
}

func (m *DockerClientMock) ContainerList(ctx context.Context, listOptions types.ContainerListOptions) ([]types.Container, error) {
mockArgs := m.Called(ctx, listOptions)
return mockArgs.Get(0).([]types.Container), mockArgs.Error(1)
}

type NodeMock struct {
mock.Mock
}

func (n *NodeMock) Command(command string, args ...string) exec.Cmd {
argsMock := append([]string{command}, args...)
mockArgs := n.Called(argsMock)
return mockArgs.Get(0).(exec.Cmd)
}

func (n *NodeMock) String() string {
args := n.Called()
return args.String(0)
}

func (n *NodeMock) Role() (string, error) {
args := n.Called()
return args.String(0), args.Error(1)
}

func (n *NodeMock) IP() (ipv4 string, ipv6 string, err error) {
args := n.Called()
return args.String(0), args.String(1), args.Error(2)
}

func (n *NodeMock) SerialLogs(writer io.Writer) error {
args := n.Called(writer)
return args.Error(0)
}

func (n *NodeMock) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd {
mockArgs := n.Called(nil)
return mockArgs.Get(0).(exec.Cmd)
}

func TestRunsOnWrongPort(t *testing.T) {
// Mock node
mockNode := &NodeMock{}
mockNode.On("Role").Return(constants.ControlPlaneNodeRoleValue, nil)
mockNode.On("String").Return("test-cluster")

mockNodes := []nodes.Node{
mockNode,
}

// Mock provider
mockProvider := &mockProvider{}
mockProvider.On("ListNodes", "test-cluster").Return(mockNodes, nil)

cluster := &Cluster{
name: "test-cluster",
provider: mockProvider,
cfg: util.TemplateConfig{
Port: "8080",
},
}

// Test when everything works fine
container1 := types.Container{
Names: []string{"/test-cluster"},
Ports: []types.Port{
{
PublicPort: uint16(8080),
},
},
}
// Mock Docker client
mockDockerClient1 := &DockerClientMock{}
mockDockerClient1.On("ContainerList", mock.Anything, mock.Anything).Return([]types.Container{container1}, nil)

result, err := cluster.RunsOnRightPort(mockDockerClient1, context.Background())
assert.NoError(t, err)
assert.True(t, result)

// Test when there's an error from the provider
container2 := types.Container{
Names: []string{"/test-cluster"},
Ports: []types.Port{
{
PublicPort: uint16(9090),
},
},
}
// Mock Docker client
mockDockerClient2 := &DockerClientMock{}
mockDockerClient2.On("ContainerList", mock.Anything, mock.Anything).Return([]types.Container{container2}, nil)
result, err = cluster.RunsOnRightPort(mockDockerClient2, context.Background())

assert.NoError(t, err)
assert.False(t, result)
}

0 comments on commit 016c309

Please sign in to comment.