Skip to content

Commit

Permalink
Automate setup of Local Development Environment
Browse files Browse the repository at this point in the history
  • Loading branch information
tomekjarosik committed Nov 21, 2024
1 parent 26e51f2 commit 1a33733
Show file tree
Hide file tree
Showing 12 changed files with 975 additions and 9 deletions.
2 changes: 1 addition & 1 deletion pkg/fugaci/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type Config struct {

KubeletEndpointPort int32 `mapstructure:"kubeletEndpointPort"`
// Needs to be reachable by Kubernetes control plane
internalIP string `mapstructure:"internalIP"`
InternalIP string `mapstructure:"internalIP"`

TLS struct {
KeyPath string `mapstructure:"keyPath"`
Expand Down
2 changes: 2 additions & 0 deletions pkg/fugaci/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func (s *Node) conditions() []v1.NodeCondition {

func (s *Node) addresses() ([]v1.NodeAddress, error) {
if s.internalIP == nil {
log.Printf("internalIP not specified, guessing from available interfaces")
ip, err := getInternalIP()
if err != nil {
return nil, err
Expand Down Expand Up @@ -326,6 +327,7 @@ func NewNode(fugaciVersion string, cfg Config) Node {

return Node{
name: cfg.NodeName,
internalIP: net.ParseIP(cfg.InternalIP),
fugaciVersion: fugaciVersion,
curieVersion: curieVersion,
kubeletEndpointPort: cfg.KubeletEndpointPort,
Expand Down
1 change: 1 addition & 0 deletions tools/docker/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kubeconfig.yaml
6 changes: 3 additions & 3 deletions tools/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ services:

server:
image: "rancher/k3s:${K3S_VERSION:-latest}"
command: server
command: server --tls-san "${FUGACI_K3S_SERVER_IP_ADDRESS:?err}"
tmpfs:
- /run
- /var/run
Expand All @@ -31,8 +31,8 @@ services:
# This is just so that we get the kubeconfig file out
- .:/output
ports:
- "16443:6443" # Kubernetes API Server
- "10080:80" # Ingress controller port 80
- "16443:6443" # Kubernetes API Server
- "10080:80" # Ingress controller port 80
- "10443:443" # Ingress controller port 443

agent:
Expand Down
32 changes: 27 additions & 5 deletions tools/generate-node-tls-certs.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/bin/bash -e

# Ensure running in the directory containing the cfssl configuration
cd "$(dirname "$0")"

# Input Parameters
Expand Down Expand Up @@ -77,15 +76,38 @@ spec:
- server auth
EOF



# Step 5: Approve the CSR in Kubernetes
kubectl certificate approve ${CSR_NAME}

# Wait for the certificate to be issued
echo "Waiting for certificate to be issued..."
for i in {1..15}; do
CERT=$(kubectl get csr "${CSR_NAME}" -o jsonpath='{.status.certificate}')
if [[ -n "${CERT}" ]]; then
echo "Certificate has been issued."
break
fi
echo "Waiting for TLS certificates to be issued"
sleep 1
done

if [[ -z "${CERT}" ]]; then
echo "Error: Certificate was not issued for CSR ${CSR_NAME}."
exit 1
fi

# Step 6: Retrieve the signed certificate
kubectl get csr ${CSR_NAME} -o jsonpath='{.status.certificate}' | base64 --decode > ${CRT_FILE}
echo "${CERT}" | base64 --decode > "${CRT_FILE}"

# Verify that the certificate file was created
if [[ ! -f "${CRT_FILE}" ]]; then
echo "Error: Certificate file ${CRT_FILE} was not created."
exit 1
fi

kubectl get csr ${CSR_NAME} -o yaml

kubectl get csr ${CSR_NAME} -o yaml
rm "${NODE_NAME}.csr"

echo "Certificate signing request, key, and certificate (once approved) have been generated:"
echo "CSR: ${CSR_FILE}"
Expand Down
38 changes: 38 additions & 0 deletions tools/localdevenv/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

# Local Development Environment Setup

This script sets up a local development environment for testing and provisioning resources.

## Prerequisites

Ensure the following are installed and properly configured:

- **Environment Variables**:
- **K3S_TOKEN**: Token for K3S.
- **FUGACI_MAC_WORKSTATION_IP_ADDRESS**: IP address of the Mac workstation reachable from K3S server.
- **FUGACI_K3S_SERVER_IP_ADDRESS**: IP address of the K3S server reachable from Mac workstation.

- **Executables**:
- kubectl
- cfssl
- cfssljson

- **SSH Connectivity**:
- SSH access to the machine named **mac-workstation**.
- The binary 'curie' must be present on **mac-workstation**.

## Usage

Run the script using the following command:
```sh
go run tools/localdevenv
```
## Description

1. **Verification**:
- Checks required environment variables are set.
- Verifies required executables are installed.
- Validates SSH connectivity and necessary binaries on the remote machine.

2. **Provisioning**:
- Provisions the required resources if all checks pass.
165 changes: 165 additions & 0 deletions tools/localdevenv/checklist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package main

import (
"bufio"
"fmt"
"os"
"os/exec"
"strings"
)

// Check Define a type for the checks
type Check struct {
name string
fn func() error
}

type Checklist struct {
checks []Check
}

func (c *Checklist) addCheck(name string, f func() error) {
c.checks = append(c.checks, Check{name, f})
}

func (c *Checklist) addCheckForEnvVariable(envVarName string) {
c.addCheck(fmt.Sprintf("%s environment variable", envVarName), checkEnvVarExists(envVarName))
}

func (c *Checklist) addExecutableInstallationCheck(executableName string) {
c.addCheck(fmt.Sprintf("%s is installed", executableName), checkBinaryExists(executableName))
}

func (c *Checklist) verify() bool {
allPassed := true
for _, c := range c.checks {
err := c.fn()
if err != nil {
fmt.Printf("❌ %s: %v\n", c.name, err)
allPassed = false
} else {
fmt.Printf("✅ %s\n", c.name)
}
}
return allPassed
}

func checkEnvVarExists(envVarName string) func() error {
return func() error {
if os.Getenv(envVarName) == "" {
return fmt.Errorf("%s environment variable is not set", envVarName)
}
return nil
}
}

func checkBinaryExists(binary string) func() error {
return func() error {
if _, err := exec.LookPath(binary); err != nil {
return fmt.Errorf("'%s' is not installed", binary)
}
return nil
}
}

func checkSSHConnectivity(sshNodeName string) func() error {
return func() error {
cmd := exec.Command("ssh", "-q", sshNodeName, "exit")
if err := cmd.Run(); err != nil {
return fmt.Errorf("cannot connect to '%s' via SSH", sshNodeName)
}
return nil
}
}

func checkBinaryExistsRemotely(sshNodeName string) func() error {
return func() error {
cmd := exec.Command("ssh", sshNodeName, "PATH=/usr/local/bin:$PATH command -v curie")
if err := cmd.Run(); err != nil {
return fmt.Errorf("'curie' is not installed or not in PATH on '%s'", sshNodeName)
}
return nil
}
}

func checkDockerInstallation() error {
// Check if 'docker' is installed
if _, err := exec.LookPath("docker"); err != nil {
return fmt.Errorf("docker is not installed")
}

// Get 'docker version'
cmd := exec.Command("docker", "version")
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error getting Docker version: %v\nOutput: %s", err, output)
}
// Optionally, print Docker version
// fmt.Printf("Docker version:\n%s\n", output)

// Check if 'docker compose' or 'docker-compose' is available
// Try 'docker compose version'
cmd = exec.Command("docker", "compose", "version")
output, err = cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("Docker Compose is not installed.\nOutput: %s", output)
}

return nil
}

func checkIPAddressBehindSSHMatchesEnvVar(sshNodeName string, envVarName string) error {
alias := sshNodeName
ipFromEnvVar := os.Getenv(envVarName)
if ipFromEnvVar == "" {
return fmt.Errorf("%s environment variable is not set", envVarName)
}

// Run 'ssh -v m1 exit' and capture stderr
cmd := exec.Command("ssh", "-v", alias, "exit")
stderrPipe, err := cmd.StderrPipe()
if err != nil {
return fmt.Errorf("failed to get stderr pipe: %v", err)
}

// Start the command
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start ssh command: %v", err)
}

// Read stderr output
scanner := bufio.NewScanner(stderrPipe)
var connectedIP string
for scanner.Scan() {
line := scanner.Text()
// Look for lines like: "debug1: Connecting to example.com [192.168.1.100] port 22."
if strings.Contains(line, "Connecting to") && strings.Contains(line, "port") {
// Extract the IP address from the line
parts := strings.Split(line, "[")
if len(parts) > 1 {
ipPart := parts[1]
ipParts := strings.Split(ipPart, "]")
if len(ipParts) > 0 {
connectedIP = ipParts[0]
break
}
}
}
}

// Wait for the command to finish
if err := cmd.Wait(); err != nil {
// Ignore exit errors since we're only interested in the connection info
}

if connectedIP == "" {
return fmt.Errorf("could not extract IP address from ssh logs")
}

if connectedIP != ipFromEnvVar {
return fmt.Errorf("%s (%s) does not match SSH '%s' IP (%s)",
envVarName, ipFromEnvVar, connectedIP, sshNodeName)
}

return nil
}
Loading

0 comments on commit 1a33733

Please sign in to comment.