oculus stable
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Colin 2024-06-11 10:24:40 -04:00
parent 56f168efb7
commit 653688c6a0
47 changed files with 859 additions and 439 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
diffs
build_logs

145
.woodpecker.yml Normal file
View File

@ -0,0 +1,145 @@
# build 1
labels:
hostname: "macmini7"
clone:
git:
image: woodpeckerci/plugin-git
settings:
partial: false
depth: 1
steps:
# Build Step for staging Branch
build-staging:
name: build-staging
image: woodpeckerci/plugin-docker-buildx
secrets: [REGISTRY_USER, REGISTRY_PASSWORD]
volumes:
- /var/run/docker.sock:/var/run/docker.sock
commands:
- echo "Building application for staging branch"
- echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us
- echo compose build
- docker compose -f docker-compose.staging.yml build --no-cache
when:
branch: main
event: push
# path:
# include: [ 'stack.production.yml', 'stack.staging.yml', 'docker-compose.staging.yml', 'docker-compose.production.yml', 'Dockerfile', '*.tests.ts' ]
deploy-new:
name: deploy-new
when:
branch: main
# path:
# include: [ 'stack.production.yml', 'stack.staging.yml', 'docker-compose.staging.yml', 'docker-compose.production.yml', 'Dockerfile', '*.tests.ts' ]
image: woodpeckerci/plugin-docker-buildx
secrets: [REGISTRY_USER, REGISTRY_PASSWORD]
volumes:
- /var/run/docker.sock:/var/run/docker.sock
commands:
- echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us
- echo compose push
- docker compose -f docker-compose.staging.yml push
# - docker stack deploy --with-registry-auth -c ./stack.staging.yml $${CI_REPO_NAME}-staging
# # Wait for Deploy to Complete
# wait-for-deploy-staging:
# name: wait-for-deploy-staging
# image: woodpeckerci/plugin-git
# commands:
# - echo "Waiting for staging deploy step to complete rollout."
# - sleep 60
# when:
# - branch: main
# - event: push
# # Run Automated Tests on staging Branch
# test-staging:
# name: run-tests-staging
# image: git.nixc.us/colin/playwright:latest
# secrets: [ base_url ]
# when:
# - branch: main
# - event: push
# - path:
# include: [ 'tests/', 'src/','docker-compose.staging.yml', 'docker-compose.production.yml', '*.tests.ts' ] # Specify paths relevant to tests
# volumes:
# - /var/run/docker.sock:/var/run/docker.sock:ro
cleanup-staging:
name: cleanup-staging
when:
branch: main
# path:
# include: [ 'stack.production.yml', 'stack.staging.yml', 'docker-compose.staging.yml', 'docker-compose.production.yml', 'Dockerfile', '*.tests.ts' ]
image: woodpeckerci/plugin-docker-buildx
secrets: [REGISTRY_USER, REGISTRY_PASSWORD]
volumes:
- /var/run/docker.sock:/var/run/docker.sock
commands:
# - docker stack rm $${CI_REPO_NAME}-staging
## added fault tolerance for docker stack rm
- for i in {1..5}; do docker stack rm ${CI_REPO_NAME}-staging && break || sleep 10; done
- docker compose -f docker-compose.staging.yml down
- docker compose -f docker-compose.staging.yml rm -f
# Build Step for staging Branch
build-push-production:
name: build-push-production
image: woodpeckerci/plugin-docker-buildx
secrets: [REGISTRY_USER, REGISTRY_PASSWORD]
volumes:
- /var/run/docker.sock:/var/run/docker.sock
commands:
- echo "Building application for staging branch"
- echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us
- echo compose build
- docker compose -f docker-compose.production.yml build --no-cache
- docker compose -f docker-compose.production.yml push
when:
branch: main
event: [push, cron]
# path:
# include: [ 'stack.production.yml', 'stack.staging.yml', 'docker-compose.staging.yml', 'docker-compose.production.yml', 'Dockerfile', '*.tests.ts' ]
# Deploy to Production Branch
deploy-production:
name: deploy-production
image: woodpeckerci/plugin-docker-buildx
secrets: [REGISTRY_USER, REGISTRY_PASSWORD]
volumes:
- /var/run/docker.sock:/var/run/docker.sock
commands:
- echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us
- docker stack deploy --with-registry-auth -c ./stack.production.yml $${CI_REPO_NAME}
# - docker image rm git.nixc.us/colin/$${CI_REPO_NAME}:production
when:
branch: main
event: [push, cron]
# path:
# include: [ 'stack.production.yml', 'stack.staging.yml', 'docker-compose.staging.yml', 'docker-compose.production.yml', 'Dockerfile', '*.tests.ts' ]
# # Wait for Deploy to Complete
# wait-for-deploy-production:
# name: wait-for-deploy-production
# image: woodpeckerci/plugin-git
# commands:
# - echo "Waiting for deploy step to complete rollout."
# - sleep 60
# when:
# branch: main
# event: push
# # Run Post-Deployment Smoke Tests
# post-deploy-smoke-tests-git-nixc-us:
# name: run-post-deploy-smoke-tests-git-nixc-us
# image: git.nixc.us/colin/playwright:latest
# # secrets: [TEST_USER, TEST_PASSWORD]
# environment:
# - BASE_URL=https://git.nixc.us
# when:
# branch: main
# event: push
# # path:
# # include: [ 'stack.production.yml', 'stack.staging.yml', 'docker-compose.staging.yml', 'docker-compose.production.yml', 'Dockerfile', '*.tests.ts' ]

108
README.md Normal file
View File

@ -0,0 +1,108 @@
<!-- build 0 -->
# Oculus Toolchain
Oculus is a toolchain for monitoring and handling Docker container diffs. It includes three main components:
- **filter**: Filters the diff outputs based on ignore patterns.
- **api**: Provides an API to fetch container details and diffs.
- **main**: The main monitoring application that uses the API and filter components to manage container diffs.
## Installation
To install the Oculus toolchain, you can use the provided `install.sh` script. This script will download and install the appropriate binaries for your system.
### Prerequisites
- **curl**: Make sure `curl` is installed on your system.
- **sudo**: Required if you do not have write permission to `/usr/local/bin`.
### Installation Steps
1. **Download the Install Script**:
```sh
curl -sSL https://git.nixc.us/colin/Oculus/raw/branch/main/install.sh -o install.sh
```
2. **Make the Script Executable**:
```sh
chmod +x install.sh
```
3. **Run the Install Script**:
```sh
./install.sh
```
This will download and install the following components to `/usr/local/bin`:
- `oculus_filter`
- `oculus_api`
- `oculus_main`
## Usage
After installation, you can start using the Oculus toolchain. Below are the basic usage instructions for each component:
### Starting the API Server
To start the API server, run:
```sh
oculus_api
```
This will start the API server on port 8080.
### Running the Main Monitoring Application
Before running the main monitoring application, ensure that the API server is running and the environment variables are set:
```sh
export API_ADDRESS="http://localhost:8080"
export GLITCHTIP_DSN="your-glitchtip-dsn"
oculus_main
```
### Filtering Diff Outputs
To manually filter a diff output file, you can use the `filter` component:
```sh
oculus_filter /path/to/diff/file "ignore_pattern1" "ignore_pattern2"
```
## Docker Integration
You can integrate Oculus into your Docker setup by including the installation script in your Dockerfile. Below is an example Dockerfile:
```dockerfile
FROM ubuntu:latest
# Install dependencies
RUN apt-get update && apt-get install -y curl
# Download and run the Oculus install script
RUN curl -sSL https://git.nixc.us/colin/Oculus/raw/branch/main/install.sh -o install.sh && \
chmod +x install.sh && \
./install.sh
# Set environment variables
ENV API_ADDRESS="http://localhost:8080"
ENV GLITCHTIP_DSN="your-glitchtip-dsn"
# Start the API server and the main application
CMD ["sh", "-c", "oculus_api & oculus_main"]
```
### Building the Docker Image
To build the Docker image, run:
```sh
docker build -t oculus-toolchain .
```
### Running the Docker Container
To run the Docker container, use:
```sh
docker run -d --name oculus-toolchain oculus-toolchain
```

115
api.go Normal file
View File

@ -0,0 +1,115 @@
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os/exec"
"strings"
)
type ContainerInfo struct {
ID string
Name string
CName string
Interval string
Ignores []string
}
func listContainersHandler(w http.ResponseWriter, r *http.Request) {
containers, err := listContainers()
if err != nil {
http.Error(w, fmt.Sprintf("Error listing containers: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(containers); err != nil {
http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError)
}
}
func getDiffHandler(w http.ResponseWriter, r *http.Request) {
containerID := r.URL.Query().Get("id")
if containerID == "" {
http.Error(w, "Missing container ID", http.StatusBadRequest)
return
}
diffOutput, err := getDiffOutput(containerID)
if err != nil {
http.Error(w, fmt.Sprintf("Error getting diff for container %s: %v", containerID, err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(diffOutput))
}
func startServer() {
http.HandleFunc("/containers", listContainersHandler)
http.HandleFunc("/diff", getDiffHandler)
log.Println("Starting server on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Error starting server: %v", err)
}
}
func listContainers() ([]ContainerInfo, error) {
output, err := exec.Command("docker", "ps", "--format", "{{.ID}} {{.Names}} {{.Label \"oculus.enable\"}} {{.Label \"oculus.interval\"}} {{.Label \"oculus.cname\"}} {{.Label \"oculus.ignores\"}}").Output()
if err != nil {
return nil, err
}
lines := strings.Split(string(output), "\n")
var containers []ContainerInfo
for _, line := range lines {
if line == "" {
continue
}
parts := strings.Fields(line)
if len(parts) < 3 || parts[2] == "" {
continue
}
id := parts[0]
name := parts[1]
interval := "300s"
if len(parts) > 3 && parts[3] != "" {
interval = parts[3]
}
cname := name
if len(parts) > 4 && parts[4] != "" {
cname = parts[4]
}
ignores := []string{}
if len(parts) > 5 && parts[5] != "" {
ignores = strings.Split(parts[5], ",")
}
containers = append(containers, ContainerInfo{
ID: id,
Name: name,
CName: cname,
Interval: interval,
Ignores: ignores,
})
}
return containers, nil
}
func getDiffOutput(containerID string) (string, error) {
cmd := exec.Command("docker", "diff", containerID)
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("docker diff failed: %w", err)
}
return string(output), nil
}
func main() {
startServer()
}

View File

@ -1,70 +1,75 @@
#!/bin/bash
# Default architecture
DEFAULT_ARCH="linux/amd64"
ARCHITECTURES=("darwin/arm64" "linux/amd64" "linux/arm64" "darwin/amd64" "windows/amd64")
PROJECT_NAME=$(basename "$(pwd)")
# Supported architectures (adjust as needed)
ARCHITECTURES=("linux/amd64" "linux/arm64" "linux/arm/v7" "darwin/amd64" "darwin/arm64")
# Ensure all necessary directories exist and go modules are ready
prepare_build() {
# Create necessary directories if they don't exist
mkdir -p dist
mkdir -p build_logs
# Initialize go modules if go.mod does not exist
mkdir -p dist build_logs
if [ ! -f go.mod ]; then
echo "Initializing Go modules"
go mod init yourmodule # Replace 'yourmodule' with your actual module name or path
go mod init "$PROJECT_NAME"
fi
# Fetch and ensure all dependencies are up to date
echo "Checking dependencies..."
go mod tidy
}
# Build function
build_binary() {
os=$1
arch=$2
output_name="oculus"
local os=$1
local arch=$2
local output_main="dist/${PROJECT_NAME}_${os}_${arch}_main"
local output_filter="dist/${PROJECT_NAME}_${os}_${arch}_filter"
local output_api="dist/${PROJECT_NAME}_${os}_${arch}_api"
if [[ "$os/$arch" != "$DEFAULT_ARCH" ]]; then
output_name="${output_name}_${os}_${arch}"
env GOOS=$os GOARCH=$arch go build -o $output_main main.go &> "build_logs/${os}_${arch}_main.log"
if [ $? -ne 0 ]; then
echo "Build failed for $os/$arch main" >> "build_logs/error.log"
else
echo "Build succeeded for $os/$arch main"
fi
output_name="dist/${output_name}"
# Dynamic Linking
echo "Building dynamically linked for ${os}/${arch} -> ${output_name}"
GOOS=${os} GOARCH=${arch} go build -o ${output_name} main.go 2>build_logs/${os}_${arch}_build.log
if [ $? -eq 0 ]; then
echo "Successfully built ${output_name}"
env GOOS=$os GOARCH=$arch go build -o $output_filter filter.go &> "build_logs/${os}_${arch}_filter.log"
if [ $? -ne 0 ]; then
echo "Build failed for $os/$arch filter" >> "build_logs/error.log"
else
echo "Failed to build ${output_name}. Check build_logs/${os}_${arch}_build.log for errors."
echo "Build succeeded for $os/$arch filter"
fi
# Static Linking
if [[ "$os" == "linux" ]]; then # Typically, static linking is most relevant for Linux environments
static_output_name="${output_name}_static"
echo "Building statically linked for ${os}/${arch} -> ${static_output_name}"
CGO_ENABLED=0 GOOS=${os} GOARCH=${arch} go build -a -ldflags '-extldflags "-static"' -o ${static_output_name} main.go 2>build_logs/${os}_${arch}_static_build.log
if [ $? -eq 0 ]; then
echo "Successfully built ${static_output_name}"
env GOOS=$os GOARCH=$arch go build -o $output_api api.go &> "build_logs/${os}_${arch}_api.log"
if [ $? -ne 0 ]; then
echo "Build failed for $os/$arch api" >> "build_logs/error.log"
else
echo "Failed to build ${static_output_name}. Check build_logs/${os}_${arch}_static_build.log for errors."
echo "Build succeeded for $os/$arch api"
fi
if [ "$os" == "linux" ]; then
env GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o "${output_main}_static" main.go &> "build_logs/${os}_${arch}_main_static.log"
if [ $? -ne 0 ]; then
echo "Static build failed for $os/$arch main" >> "build_logs/error.log"
else
echo "Static build succeeded for $os/$arch main"
fi
env GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o "${output_filter}_static" filter.go &> "build_logs/${os}_${arch}_filter_static.log"
if [ $? -ne 0 ]; then
echo "Static build failed for $os/$arch filter" >> "build_logs/error.log"
else
echo "Static build succeeded for $os/$arch filter"
fi
env GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o "${output_api}_static" api.go &> "build_logs/${os}_${arch}_api_static.log"
if [ $? -ne 0 ]; then
echo "Static build failed for $os/$arch api" >> "build_logs/error.log"
else
echo "Static build succeeded for $os/$arch api"
fi
fi
}
# Main Build Process
prepare_build
for arch in "${ARCHITECTURES[@]}"; do
IFS='/' read -r -a parts <<< "$arch" # Split architecture string
os=${parts[0]}
arch=${parts[1]}
main() {
prepare_build
for arch in "${ARCHITECTURES[@]}"; do
IFS="/" read -r os arch <<< "$arch"
build_binary $os $arch
done
echo "Build process completed."
done
echo "Build process completed."
}
main

View File

@ -1,6 +0,0 @@
# command-line-arguments
./main.go:9:5: "os" imported and not used
./main.go:11:5: "strings" imported and not used
./main.go:12:5: "time" imported and not used
./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used
./main.go:34:70: undefined: types.ContainerListOptions

View File

@ -1,6 +0,0 @@
# command-line-arguments
./main.go:9:5: "os" imported and not used
./main.go:11:5: "strings" imported and not used
./main.go:12:5: "time" imported and not used
./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used
./main.go:34:70: undefined: types.ContainerListOptions

View File

@ -1,6 +0,0 @@
# command-line-arguments
./main.go:9:5: "os" imported and not used
./main.go:11:5: "strings" imported and not used
./main.go:12:5: "time" imported and not used
./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used
./main.go:34:70: undefined: types.ContainerListOptions

View File

@ -1,6 +0,0 @@
# command-line-arguments
./main.go:9:5: "os" imported and not used
./main.go:11:5: "strings" imported and not used
./main.go:12:5: "time" imported and not used
./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used
./main.go:34:70: undefined: types.ContainerListOptions

View File

@ -1,6 +0,0 @@
# command-line-arguments
./main.go:9:5: "os" imported and not used
./main.go:11:5: "strings" imported and not used
./main.go:12:5: "time" imported and not used
./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used
./main.go:34:70: undefined: types.ContainerListOptions

View File

@ -1,6 +0,0 @@
# command-line-arguments
./main.go:9:5: "os" imported and not used
./main.go:11:5: "strings" imported and not used
./main.go:12:5: "time" imported and not used
./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used
./main.go:34:70: undefined: types.ContainerListOptions

View File

@ -1,6 +0,0 @@
# command-line-arguments
./main.go:9:5: "os" imported and not used
./main.go:11:5: "strings" imported and not used
./main.go:12:5: "time" imported and not used
./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used
./main.go:34:70: undefined: types.ContainerListOptions

View File

@ -1,6 +0,0 @@
# command-line-arguments
./main.go:9:5: "os" imported and not used
./main.go:11:5: "strings" imported and not used
./main.go:12:5: "time" imported and not used
./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used
./main.go:34:70: undefined: types.ContainerListOptions

BIN
dist/oculus_darwin_amd64_api vendored Executable file

Binary file not shown.

BIN
dist/oculus_darwin_amd64_filter vendored Executable file

Binary file not shown.

BIN
dist/oculus_darwin_amd64_main vendored Executable file

Binary file not shown.

BIN
dist/oculus_darwin_arm64_api vendored Executable file

Binary file not shown.

BIN
dist/oculus_darwin_arm64_filter vendored Executable file

Binary file not shown.

BIN
dist/oculus_darwin_arm64_main vendored Executable file

Binary file not shown.

BIN
dist/oculus_linux_amd64_api vendored Executable file

Binary file not shown.

BIN
dist/oculus_linux_amd64_api_static vendored Executable file

Binary file not shown.

BIN
dist/oculus_linux_amd64_filter vendored Executable file

Binary file not shown.

BIN
dist/oculus_linux_amd64_filter_static vendored Executable file

Binary file not shown.

BIN
dist/oculus_linux_amd64_main vendored Executable file

Binary file not shown.

BIN
dist/oculus_linux_amd64_main_static vendored Executable file

Binary file not shown.

BIN
dist/oculus_linux_arm64_api vendored Executable file

Binary file not shown.

BIN
dist/oculus_linux_arm64_api_static vendored Executable file

Binary file not shown.

BIN
dist/oculus_linux_arm64_filter vendored Executable file

Binary file not shown.

BIN
dist/oculus_linux_arm64_filter_static vendored Executable file

Binary file not shown.

BIN
dist/oculus_linux_arm64_main vendored Executable file

Binary file not shown.

BIN
dist/oculus_linux_arm64_main_static vendored Executable file

Binary file not shown.

BIN
dist/oculus_windows_amd64_api vendored Executable file

Binary file not shown.

BIN
dist/oculus_windows_amd64_filter vendored Executable file

Binary file not shown.

BIN
dist/oculus_windows_amd64_main vendored Executable file

Binary file not shown.

View File

@ -0,0 +1,6 @@
services:
oculus:
build:
context: ./docker/oculus/
dockerfile: Dockerfile.production
image: git.nixc.us/colin/oculus:production

View File

@ -0,0 +1,6 @@
services:
oculus:
build:
context: ./docker/oculus/
dockerfile: Dockerfile
image: git.nixc.us/colin/oculus:staging

9
docker/oculus/Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM alpine:latest
RUN apk update && apk add --no-cache curl bash
RUN curl -sSL https://git.nixc.us/colin/Oculus/raw/branch/main/install.sh | bash
RUN curl -sSL https://git.nixc.us/Nixius/go-glitch/raw/branch/master/install.sh | bash
COPY notify.sh /notify.sh
RUN chmod +x /notify.sh
ENV GLITCHTIP_DSN=""
CMD ["/usr/local/bin/oculus"]

View File

@ -0,0 +1 @@
FROM git.nixc.us/colin/oculus:staging

0
docker/oculus/notify.sh Normal file
View File

105
filter.go Normal file
View File

@ -0,0 +1,105 @@
package main
import (
"bufio"
"fmt"
"log"
"os"
"regexp"
"strings"
)
func main() {
if len(os.Args) < 3 {
log.Fatalf("Usage: %s <file> <pattern1> [<pattern2> ... <patternN>]\n", os.Args[0])
}
filename := os.Args[1]
patterns := os.Args[2:]
// Compile patterns into regular expressions
regexPatterns := compilePatterns(patterns)
// Read the file
file, err := os.Open(filename)
if err != nil {
log.Fatalf("Error opening file: %s\n", err)
}
defer file.Close()
var filteredLines []string
var removedCount int
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Printf("Processing line: %s\n", line) // Debug print
if matchesAnyPattern(line, regexPatterns) {
fmt.Printf("Line matches pattern, removing: %s\n", line) // Debug print
removedCount++
} else {
filteredLines = append(filteredLines, line)
}
}
if err := scanner.Err(); err != nil {
log.Fatalf("Error reading file: %s\n", err)
}
// Write the filtered lines back to the file
tempFile, err := os.CreateTemp("", "filtered_output")
if err != nil {
log.Fatalf("Error creating temporary file: %s\n", err)
}
defer os.Remove(tempFile.Name())
for _, line := range filteredLines {
_, err = tempFile.WriteString(line + "\n")
if err != nil {
log.Fatalf("Error writing to temporary file: %s\n", err)
}
}
tempFile.Close()
err = os.Rename(tempFile.Name(), filename)
if err != nil {
log.Fatalf("Error renaming temporary file: %s\n", err)
}
fmt.Printf("Filtered %d lines from %s\n", removedCount, filename)
}
func compilePatterns(patterns []string) []*regexp.Regexp {
var regexPatterns []*regexp.Regexp
for _, pattern := range patterns {
// Convert wildcard pattern to regex pattern
regexPattern := strings.ReplaceAll(regexp.QuoteMeta(pattern), "\\*", ".*")
regexPattern = strings.TrimSuffix(regexPattern, `\.`) // Remove trailing dot if present
fmt.Printf("Compiled pattern: %s to regex: %s\n", pattern, regexPattern) // Debug print
re, err := regexp.Compile(regexPattern)
if err != nil {
log.Fatalf("Error compiling regex pattern: %s\n", err)
}
regexPatterns = append(regexPatterns, re)
}
return regexPatterns
}
func matchesAnyPattern(line string, patterns []*regexp.Regexp) bool {
// Get the full path including directory
parts := strings.Fields(line)
if len(parts) < 2 {
return false
}
fullPath := parts[1]
fmt.Printf("Checking filename: %s against patterns\n", fullPath) // Debug print
for _, pattern := range patterns {
if pattern.MatchString(fullPath) {
fmt.Printf("Pattern matched: %s in filename: %s\n", pattern.String(), fullPath) // Debug print
return true
}
}
return false
}

34
go.mod
View File

@ -1,35 +1,3 @@
module yourmodule
module oculus
go 1.21.1
require (
github.com/docker/docker v26.1.4+incompatible
github.com/robfig/cron/v3 v3.0.1
)
require (
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
go.opentelemetry.io/otel v1.27.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.27.0 // indirect
go.opentelemetry.io/otel/sdk v1.27.0 // indirect
go.opentelemetry.io/otel/trace v1.27.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/time v0.5.0 // indirect
gotest.tools/v3 v3.5.1 // indirect
)

123
go.sum
View File

@ -1,123 +0,0 @@
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU=
github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY=
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI=
go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A=
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ=
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=

View File

@ -1,15 +1,11 @@
#!/bin/bash
INSTALL_DIR="/usr/local/bin"
BINARY_NAME="oculus"
BASE_URL="https://git.nixc.us/colin/oculus/raw/branch/main/dist"
PROJECT_NAME=$(basename "$(pwd)")
BASE_URL="https://git.nixc.us/colin/Oculus/raw/branch/main/dist"
declare -A binaries
binaries["linux/amd64"]="oculus_linux_amd64"
binaries["linux/arm64"]="oculus_linux_arm64"
binaries["linux/arm/v7"]="oculus_linux_arm"
binaries["darwin/amd64"]="oculus_darwin_amd64"
binaries["darwin/arm64"]="oculus_darwin_arm64"
# Supported architectures
ARCHITECTURES=("linux/amd64" "linux/arm64" "linux/arm/v7" "darwin/amd64" "darwin/arm64")
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
@ -21,17 +17,21 @@ case $ARCH in
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
esac
KEY="${OS}/${ARCH}"
COMPONENTS=("filter" "api" "main")
if [[ -z "${binaries[$KEY]}" ]]; then
echo "No pre-built binary for your system architecture ($KEY)."
exit 1
fi
for COMPONENT in "${COMPONENTS[@]}"; do
BINARY_NAME="${PROJECT_NAME}_${COMPONENT}"
BINARY_URL="${BASE_URL}/${PROJECT_NAME}_${OS}_${ARCH}_${COMPONENT}"
echo "Downloading and installing $BINARY_NAME from $BINARY_URL..."
BINARY_URL="${BASE_URL}/${binaries[$KEY]}"
# Check if we have write permission to the install directory
if [ -w "${INSTALL_DIR}" ]; then
curl -sSL "$BINARY_URL" -o "${INSTALL_DIR}/${BINARY_NAME}"
chmod +x "${INSTALL_DIR}/${BINARY_NAME}"
else
sudo curl -sSL "$BINARY_URL" -o "${INSTALL_DIR}/${BINARY_NAME}"
sudo chmod +x "${INSTALL_DIR}/${BINARY_NAME}"
fi
echo "Downloading and installing $BINARY_NAME from $BINARY_URL..."
sudo curl -sSL "$BINARY_URL" -o "${INSTALL_DIR}/${BINARY_NAME}"
sudo chmod +x "${INSTALL_DIR}/${BINARY_NAME}"
echo "Installed $BINARY_NAME to $INSTALL_DIR"
echo "Installed $BINARY_NAME to $INSTALL_DIR"
done

341
main.go
View File

@ -1,182 +1,255 @@
package main
import (
"context"
"crypto/md5"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/robfig/cron/v3"
)
const (
notifyScript = "/notify.sh"
)
// ContainerDiff represents the state of a container
type ContainerDiff struct {
type ContainerInfo struct {
ID string
Image string
Labels map[string]string
Name string
CName string
Interval string
Ignores []string
}
// getRunningContainers fetches the list of running containers with relevant labels
func getRunningContainers(cli *client.Client) ([]ContainerDiff, error) {
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
type MonitoredContainer struct {
Info ContainerInfo
LastChecked time.Time
}
var (
apiAddress string
glitchtipDSN string
notifiedChanges = make(map[string]string)
monitoredContainers = make(map[string]*MonitoredContainer)
mu sync.Mutex
)
func init() {
apiAddress = os.Getenv("API_ADDRESS")
if apiAddress == "" {
apiAddress = "http://localhost:8080"
}
glitchtipDSN = os.Getenv("GLITCHTIP_DSN")
if glitchtipDSN == "" {
log.Fatal("GLITCHTIP_DSN environment variable is not set")
}
}
func main() {
log.Println("Starting Oculus...")
// Ensure the diffs directory exists
err := os.MkdirAll("./diffs", os.ModePerm)
if err != nil {
return nil, err
log.Fatalf("Error creating diffs directory: %v", err)
}
var diffs []ContainerDiff
for _, container := range containers {
if _, ok := container.Labels["oculus.containerid"]; ok {
diffs = append(diffs, ContainerDiff{
ID: container.ID,
Image: container.Image,
Labels: container.Labels,
})
}
for {
err := fetchAndMonitorContainers()
if err != nil {
log.Printf("Error in fetching and monitoring containers: %v", err)
}
return diffs, nil
time.Sleep(1 * time.Second)
}
}
// saveDiffs writes the container diffs to a file
func saveDiffs(diffs []ContainerDiff, filePath string) error {
data, err := json.Marshal(diffs)
func fetchAndMonitorContainers() error {
containers, err := fetchContainers()
if err != nil {
return err
}
return ioutil.WriteFile(filePath, data, 0644)
}
// loadDiffs reads the container diffs from a file
func loadDiffs(filePath string) ([]ContainerDiff, error) {
data, err := ioutil.ReadFile(filePath)
for _, container := range containers {
mu.Lock()
if _, exists := monitoredContainers[container.ID]; !exists {
interval, err := time.ParseDuration(container.Interval)
if err != nil {
return nil, err
}
var diffs []ContainerDiff
err = json.Unmarshal(data, &diffs)
return diffs, err
}
// profileContainers logs the current state of containers to a file
func profileContainers(cli *client.Client) {
diffs, err := getRunningContainers(cli)
if err != nil {
log.Fatalf("Error profiling containers: %v", err)
}
for _, diff := range diffs {
if diff.Labels["oculus.mode"] == "profile" {
filePath := fmt.Sprintf("%s.json", diff.Labels["oculus.containerid"])
err := saveDiffs([]ContainerDiff{diff}, filePath)
if err != nil {
log.Fatalf("Error saving diffs: %v", err)
}
}
}
log.Println("Profiled current containers and saved to file")
}
// compareContainers compares the current state of containers with the saved state
func compareContainers(cli *client.Client) {
currentDiffs, err := getRunningContainers(cli)
if err != nil {
log.Fatalf("Error getting current containers: %v", err)
}
for _, current := range currentDiffs {
if current.Labels["oculus.mode"] == "monitor" {
filePath := fmt.Sprintf("%s.json", current.Labels["oculus.containerid"])
savedDiffs, err := loadDiffs(filePath)
if err != nil {
log.Printf("Error loading saved diffs for %s: %v", current.ID, err)
log.Printf("Invalid interval for container %s (%s): %v", container.Name, container.ID, err)
mu.Unlock()
continue
}
if len(savedDiffs) > 0 {
saved := savedDiffs[0]
if current.Image != saved.Image {
log.Printf("Container %s changed: Image %s -> %s", current.ID, saved.Image, current.Image)
runNotifyScript(current.ID, current.Image, saved.Image)
}
if current.Labels["oculus.ignorelist"] != saved.Labels["oculus.ignorelist"] {
log.Printf("Container %s ignore list changed: %s -> %s", current.ID, saved.Labels["oculus.ignorelist"], current.Labels["oculus.ignorelist"])
runNotifyScript(current.ID, current.Image, saved.Image)
}
} else {
log.Printf("New container detected: ID %s, Image %s", current.ID, current.Image)
runNotifyScript(current.ID, current.Image, "")
monitoredContainers[container.ID] = &MonitoredContainer{
Info: container,
LastChecked: time.Now().Add(-interval),
}
}
mu.Unlock()
}
}
// runNotifyScript executes the notification script with the container details
func runNotifyScript(containerID, newImage, oldImage string) {
cmd := exec.Command(notifyScript, containerID, newImage, oldImage)
err := cmd.Run()
var wg sync.WaitGroup
for _, monitoredContainer := range monitoredContainers {
wg.Add(1)
go func(mc *MonitoredContainer) {
defer wg.Done()
interval, err := time.ParseDuration(mc.Info.Interval)
if err != nil {
log.Printf("Error running notify script: %v", err)
}
}
// watchDockerEvents listens for Docker events and triggers comparisons
func watchDockerEvents(cli *client.Client) {
eventFilter := filters.NewArgs()
eventFilter.Add("type", "container")
eventFilter.Add("event", "start")
eventFilter.Add("event", "stop")
eventFilter.Add("event", "destroy")
messages, errs := cli.Events(context.Background(), types.EventsOptions{
Filters: eventFilter,
})
for {
select {
case event := <-messages:
log.Printf("Docker event received: %s for container %s", event.Action, event.ID)
compareContainers(cli)
case err := <-errs:
log.Printf("Error watching Docker events: %v", err)
log.Printf("Invalid interval for container %s (%s): %v", mc.Info.Name, mc.Info.ID, err)
return
}
mu.Lock()
if time.Since(mc.LastChecked) >= interval {
mc.LastChecked = time.Now()
mu.Unlock()
checkAndNotify(mc.Info)
} else {
mu.Unlock()
}
}(monitoredContainer)
}
wg.Wait()
return nil
}
func fetchContainers() ([]ContainerInfo, error) {
resp, err := http.Get(fmt.Sprintf("%s/containers", apiAddress))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("error fetching containers: %s", resp.Status)
}
var containers []ContainerInfo
if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil {
return nil, err
}
return containers, nil
}
func fetchDiffOutput(containerID string) (string, error) {
resp, err := http.Get(fmt.Sprintf("%s/diff?id=%s", apiAddress, containerID))
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("error fetching diff: %s", resp.Status)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
func checkAndNotify(container ContainerInfo) {
diffOutput, err := fetchDiffOutput(container.ID)
if err != nil {
log.Printf("Error getting diffs for container %s (%s): %v", container.Name, container.ID, err)
return
}
filteredOutput, err := filterDiffOutput(diffOutput, container.CName, container.Ignores)
if err != nil {
log.Printf("Error filtering diffs for container %s (%s): %v", container.Name, container.ID, err)
return
}
log.Printf("Filtered diff output for container %s: %s", container.Name, filteredOutput)
if filteredOutput != "" {
diffHash := fmt.Sprintf("%x", md5.Sum([]byte(filteredOutput)))
log.Printf("Diff hash for container %s: %s", container.Name, diffHash)
mu.Lock()
lastNotifiedHash, notified := notifiedChanges[container.ID]
mu.Unlock()
if !notified || lastNotifiedHash != diffHash {
filename := filepath.Join("diffs", fmt.Sprintf("%s.diff", container.CName))
err := writeToFile(filename, filteredOutput)
if err != nil {
log.Printf("Error writing diff to file: %v", err)
return
}
err = sendNotification(filteredOutput)
if err != nil {
log.Printf("Error sending notification for container %s: %v", container.ID, err)
} else {
mu.Lock()
notifiedChanges[container.ID] = diffHash
mu.Unlock()
log.Printf("Notification sent and hash updated for container: %s (%s)", container.Name, container.ID)
}
} else {
log.Printf("No new changes detected for container: %s (%s)", container.Name, container.ID)
}
} else {
log.Printf("No significant changes detected for container: %s (%s)", container.Name, container.ID)
}
}
// scheduler sets up the cron jobs for profiling
func scheduler(cli *client.Client) {
c := cron.New()
c.AddFunc("@every 1h", func() { profileContainers(cli) })
c.Start()
}
func filterDiffOutput(diffOutput, cname string, ignores []string) (string, error) {
filename := filepath.Join("diffs", fmt.Sprintf("%s.diff", cname))
func main() {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if (err != nil) {
log.Fatalf("Error creating Docker client: %v", err)
// Write the diff output to the file
err := ioutil.WriteFile(filename, []byte(diffOutput), 0644)
if err != nil {
return "", fmt.Errorf("error writing diff to file: %s", err)
}
go scheduler(cli)
// Construct the filter command
args := append([]string{filename}, ignores...)
cmd := exec.Command("filter", args...)
fullCommand := fmt.Sprintf("filter %s", strings.Join(cmd.Args, " "))
log.Printf("Running command: %s", fullCommand)
fmt.Printf("Running command: %s\n", fullCommand) // Print the command to stdout for debugging
go watchDockerEvents(cli)
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("error running filter command: %s, output: %s", err, output)
}
// Keep the program running
select {}
// Read the filtered output
filteredOutput, err := ioutil.ReadFile(filename)
if err != nil {
return "", fmt.Errorf("error reading filtered diff file: %s", err)
}
return string(filteredOutput), nil
}
func writeToFile(filename, content string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(content)
return err
}
func sendNotification(content string) error {
cmd := exec.Command("go-glitch")
cmd.Stdin = strings.NewReader(content)
output, err := cmd.CombinedOutput()
if err != nil {
log.Printf("go-glitch output: %s", output)
return err
}
return nil
}

3
second_container.diff Normal file
View File

@ -0,0 +1,3 @@
A /file1.txt
A /file2.txt
A /file3.txt

25
stack.production.yml Normal file
View File

@ -0,0 +1,25 @@
services:
oculus:
image: git.nixc.us/colin/oculus:production
environment:
GLITCHTIP_DSN: ""
volumes:
- /mnt/tank/persist/nixc.us/oculus/production/data:/log
- "/var/run/docker.sock:/var/run/docker.sock:ro"
deploy:
placement:
constraints:
- node.role == manager
labels:
traefik.enable: "false"
oculus.containerid: "oculus"
oculus.ignorelist: "/log/,/tmp/"
oculus.mode: "monitor"
oculus.interval: "60s"
update_config:
order: stop-first
failure_action: rollback
delay: 0s
parallelism: 1
restart_policy:
condition: on-failure

20
stack.staging.yml Normal file
View File

@ -0,0 +1,20 @@
services:
oculus:
image: git.nixc.us/colin/oculus:staging
environment:
GLITCHTIP_DSN: ""
# volumes:
# - /mnt/tank/persist/nixc.us/oculus/staging/data:/log
deploy:
# placement:
# constraints:
# - node.hostname == macmini14
labels:
traefik.enable: "false"
update_config:
order: stop-first
failure_action: rollback
delay: 0s
parallelism: 1
restart_policy:
condition: on-failure