diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0fde57e..62c7481 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,6 +16,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Install YQ + run: | + sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq &&\ + sudo chmod +x /usr/bin/yq - name: Generate Certs run: | export GOPATH=/opt/go @@ -23,7 +27,7 @@ jobs: export PATH=$GOPATH/bin:$PATH go install github.com/cloudflare/cfssl/cmd/cfssl@v1.6.3 go install github.com/cloudflare/cfssl/cmd/cfssljson@v1.6.3 - make gencert + make certs - name: Build Container run: | make container @@ -32,10 +36,3 @@ jobs: mkdir -p ${HOME}/.aws && cp data/credentials ${HOME}/.aws/ export AWS_REGION=us-west-2 go test ./pkg/api ./pkg/grpc ./pkg/pubsub ./pkg/storage - -# TODO: need to disable for now, as this fails and I can't figure out why -# - name: Test CLI -# run: | -# mkdir -p ${HOME}/.fsm/certs -# cp certs/ca.pem ${HOME}/.fsm/certs/ -# RELEASE=$(make version) BASEDIR=$(pwd) go test ./client diff --git a/Makefile b/Makefile index 60b3a80..6e5d86a 100644 --- a/Makefile +++ b/Makefile @@ -1,107 +1,87 @@ -# Copyright (c) 2022 AlertAvert.com. All rights reserved. +# Copyright (c) 2022-2024 AlertAvert.com. All rights reserved. # Created by M. Massenzio, 2022-03-14 - -GOOS ?= $(shell uname -s | tr "[:upper:]" "[:lower:]") -GOARCH ?= amd64 -GOMOD := $(shell go list -m) - -version := v0.13.1 -release := $(version)-g$(shell git rev-parse --short HEAD) -out := build/bin -server := fsm-server-$(version)_$(GOOS)-$(GOARCH) -cli := fsm-cli-$(version)_$(GOOS)-$(GOARCH) - -# CLI Configuration -cli_config := ${HOME}/.fsm - -image := massenz/statemachine -compose := docker/compose.yaml -dockerfile := docker/Dockerfile +include thirdparty/common.mk +bin := $(appname)-$(release)_$(GOOS)-$(GOARCH) # Source files & Test files definitions -# -# Edit only the packages list, when adding new functionality, -# the rest is deduced automatically. -# -pkgs := pkg/api pkg/grpc pkg/pubsub pkg/storage pkg/internal +pkgs := $(shell find pkg -mindepth 1 -type d) all_go := $(shell for d in $(pkgs); do find $$d -name "*.go"; done) test_srcs := $(shell for d in $(pkgs); do find $$d -name "*_test.go"; done) srcs := $(filter-out $(test_srcs),$(all_go)) ##@ General - -# The help target prints out all targets with their descriptions organized -# beneath their categories. The categories are represented by '##@' and the -# target descriptions by '##'. The awk commands is responsible for reading the -# entire set of makefiles included in this invocation, looking for lines of the -# file as xyz: ## something, and then pretty-format the target and help. Then, -# if there's a line with ##@ something, that gets pretty-printed as a category. -# More info on the usage of ANSI control characters for terminal formatting: -# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters -# More info on the awk command: -# http://linuxcommand.org/lc3_adv_awk.php - -.PHONY: help -help: ## Display this help. - @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) - .PHONY: clean img=$(shell docker images -q --filter=reference=$(image)) -clean: ## Cleans up the binary, container image and other data - @rm $(out)/$(server) $(out)/$(cli) - @[ ! -z $(img) ] && docker rmi $(img) || true - @rm -rf certs +clean: clean-cert ## Cleans up the binary, container image and other data + @rm -rf build/* + @find . -name "*.out" -exec rm {} \; +ifneq (,$(img)) + @docker rmi $(img) || true +endif version: ## Displays the current version tag (release) - @echo $(release) + @echo v$(version) fmt: ## Formats the Go source code using 'go fmt' @go fmt $(pkgs) ./cmd fsm-cli/client fsm-cli/cmd ##@ Development -.PHONY: build test container cov clean fmt -$(out)/$(server): cmd/main.go $(srcs) - @mkdir -p $(out) - GOOS=$(GOOS) GOARCH=$(GOARCH) go build \ - -ldflags "-X $(GOMOD)/pkg/api.Release=$(release)" \ - -o $(out)/$(server) cmd/main.go - .PHONY: build -build: $(out)/$(server) ## Builds the Statemachine server binary - -.PHONY: cli -cli: fsm-cli/cmd/main.go ## Builds the CLI client used to connect to the server - @mkdir -p $(out) - cd fsm-cli && GOOS=$(GOOS) GOARCH=$(GOARCH) go build \ - -ldflags "-X main.Release=$(version)" \ - -o ../$(out)/$(cli) cmd/main.go - -.PHONY: cli-test -cli-test: ## Run tests for the CLI Client - @mkdir -p $(cli_config)/certs - @cp certs/ca.pem $(cli_config)/certs || true - cd fsm-cli && RELEASE=$(release) BASEDIR=$(shell pwd) \ - CLI_TEST_COMPOSE=$(shell pwd)/docker/cli-test-compose.yaml \ - ginkgo test ./client - -test: $(srcs) $(test_srcs) ## Runs all tests - ginkgo $(pkgs) - -cov: $(srcs) $(test_srcs) ## Runs the Test Coverage and saves the coverage report to out/reports/cov.out - @mkdir -p out/reports - @go test -coverprofile=out/reports/cov.out $(pkgs) - @echo "Coverage report at out/reports/cov.out" +build: cmd/main.go $(srcs) ## Builds the binary + @mkdir -p build/bin + @echo "Building rel. $(release); OS/Arch: $(GOOS)/$(GOARCH) - Pkg: $(GOMOD)" + @GOOS=$(GOOS) GOARCH=$(GOARCH) go build \ + -ldflags "-X $(GOMOD)/api.Release=$(release)" \ + -o build/bin/$(bin) cmd/main.go + @echo "$(GREEN)[SUCCESS]$(RESET) Binary $(shell basename $(bin)) built" + +build/bin/$(bin): build + +.PHONY: test +test: $(srcs) $(test_srcs) check_certs ## Runs all tests + @mkdir -p build/reports + ginkgo -keepGoing -cover -coverprofile=coverage.out -outputdir=build/reports $(pkgs) + # Clean up the coverage files (they are not needed once the + # report is generated) + @find ./pkg -name "coverage.out" -exec rm {} \; + +.PHONY: watch +watch: $(srcs) $(test_srcs) ## Runs all tests every time a source or test file changes + ginkgo watch -p $(pkgs) + +build/reports/coverage.out: test ## Runs all tests and generates the coverage report + +.PHONY: coverage +coverage: build/reports/coverage.out ## Shows the coverage report in the browser + @go tool cover -html=build/reports/coverage.out + +.PHONY: all +all: build gencert test ## Builds the binary and runs all tests + +PORT ?= 7398 +.PHONY: dev +dev: build ## Runs the server binary in development mode + # FIXME: this currently does not work and should be adjusted + build/bin/$(bin) -debug -grpc-port $(PORT) ##@ Container Management # Convenience targets to run locally containers and # setup the test environments. +image := massenz/$(appname) +compose := docker/compose.yaml +dockerfile := docker/Dockerfile -container: ## Builds the container image - docker build -f $(dockerfile) -t $(image):$(release) . +.PHONY: container +container: build/bin/$(bin) ## Builds the container image + docker build -f $(dockerfile) \ + --build-arg="VERSION=$(version)" \ + -t $(image):$(release) . + @echo "$(GREEN)[SUCCESS]$(RESET) Container image $(YELLOW)$(image):$(release)$(RESET) built" .PHONY: start -start: ## Starts the Redis and LocalStack containers, and Creates the SQS Queues in LocalStack +start: ## Runs the container locally + @echo "$(GREEN)[STARTING]$(RESET) Stopping containers" @RELEASE=$(release) BASEDIR=$(shell pwd) docker compose -f $(compose) --project-name sm up redis localstack -d @sleep 3 @for queue in events notifications; do \ @@ -109,39 +89,72 @@ start: ## Starts the Redis and LocalStack containers, and Creates the SQS Queues --region us-west-2 \ sqs create-queue --queue-name $$queue; done @RELEASE=$(release) BASEDIR=$(shell pwd) docker compose -f $(compose) --project-name sm up server + @echo "$(GREEN)[SUCCESS]$(RESET) Containers started" .PHONY: stop -stop: ## Stops the Redis and LocalStack containers +stop: ## Stops the running containers + @echo "$(RED)[STOPPING]$(RESET) Stopping containers" @RELEASE=$(release) BASEDIR=$(shell pwd) docker compose -f $(compose) --project-name sm down ##@ TLS Support # # This section is WIP and subject to change - -config_dir := ssl-config -ca-csr := $(config_dir)/ca-csr.json -ca-config := $(config_dir)/ca-config.json -server-csr := $(config_dir)/localhost-csr.json - -.PHONY: gencert -gencert: $(ca-csr) $(ca-config) $(server-csr) ## Generates all certificates in the certs directory (requires cfssl and cfssl, see https://github.com/cloudflare/cfssl#installation) - cfssl gencert \ - -initca $(ca-csr) | cfssljson -bare ca - - - cfssl gencert \ - -ca=ca.pem \ - -ca-key=ca-key.pem \ - -config=$(ca-config) \ - -profile=server \ - $(server-csr) | cfssljson -bare server +# Dependency checks for the 'test' target +.PHONY: check_certs +check_certs: + @num_certs=$$(ls -1 certs/*.pem 2>/dev/null | wc -l); \ + if [ $$num_certs != 4 ]; then \ + echo "$(YELLOW)[WARN]$(RESET) No certificates found in $(shell pwd)/certs"; \ + make certs; \ + echo "$(GREEN)[SUCCESS]$(RESET) Certificates generated in $(shell pwd)/certs"; \ + else \ + echo "$(GREEN)[OK]$(RESET) Certificates found in $(shell pwd)/certs"; \ + fi + +ssl_config := ../ssl-config +ca-csr := $(ssl_config)/ca-csr.json +ca-config := $(ssl_config)/ca-config.json +server-csr := $(ssl_config)/localhost-csr.json + +.PHONY: certs +certs: ## Generates all certificates in the certs directory (requires cfssl, see https://github.com/cloudflare/cfssl#installation) @mkdir -p certs - @mv *.pem certs/ - @rm *.csr - @chmod a+r certs/* - @echo "Certificates generated in $(shell pwd)/certs" + @cd certs && \ + cfssl gencert \ + -initca $(ca-csr) 2>/dev/null | cfssljson -bare ca + @cd certs && \ + cfssl gencert \ + -ca=ca.pem \ + -ca-key=ca-key.pem \ + -config=$(ca-config) \ + -profile=server \ + $(server-csr) 2>/dev/null | cfssljson -bare server + @rm certs/*.csr + @chmod a+r certs/*.pem + @echo "$(GREEN)[SUCCESS]$(RESET) Certificates generated" .PHONY: clean-cert clean-cert: @rm -rf certs + +##@ CLI Client +# TODO: move to a separate Makefile in the subdirectory +# See: https://www.gnu.org/software/make/manual/html_node/Recursion.html +# CLI Configuration +cli := out/bin/$(appname)-cli-$(version)_$(GOOS)-$(GOARCH) +cli_config := ${HOME}/.fsm +.PHONY: cli +cli: fsm-cli/cmd/main.go ## Builds the CLI client used to connect to the server + @mkdir -p build/bin + cd fsm-cli && GOOS=$(GOOS) GOARCH=$(GOARCH) go build \ + -ldflags "-X main.Release=$(release)" \ + -o ../build/bin/$(cli) cmd/main.go + +.PHONY: cli-test +cli-test: ## Run tests for the CLI Client + @mkdir -p $(cli_config)/certs + @cp certs/ca.pem $(cli_config)/certs || true + cd fsm-cli && RELEASE=$(release) BASEDIR=$(shell pwd) \ + CLI_TEST_COMPOSE=$(shell pwd)/docker/cli-test-compose.yaml \ + ginkgo test ./client diff --git a/docker/Dockerfile b/docker/Dockerfile index 55397a3..ddafe4c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,11 +1,13 @@ # Copyright AlertAvert.com (c) 2023 # Created by M. Massenzio, 2022-04-28 -FROM golang:1.21 as builder +FROM golang:1.22 as builder WORKDIR /server COPY . . RUN rm -rf certs data +RUN wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 \ + -O /usr/bin/yq && chmod +x /usr/bin/yq RUN CGO_ENABLED=0 make build RUN CGO_ENABLED=0 go build -o build/bin/hc docker/grpc_health.go diff --git a/docker/compose.yaml b/docker/compose.yaml index c94a061..6836779 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -42,7 +42,7 @@ services: - sm-net ports: - '7398:7398' - image: "massenz/statemachine:${RELEASE}" + image: "massenz/fsm-server:${RELEASE}" environment: AWS_ENDPOINT: "http://awslocal:4566" AWS_REGION: us-west-2 diff --git a/settings.yaml b/settings.yaml new file mode 100644 index 0000000..6a31f43 --- /dev/null +++ b/settings.yaml @@ -0,0 +1,10 @@ +# Copyright (c) 2023-2024 AlertAvert.com +# All rights reserved. +# Created by Marco Massenzio, 2023-07-13 + +name: fsm-server +version: 0.12.2 +description: A StateMachine gRPC server, backed by Redis storage +author: Marco Massenzio (marco@alertavert.com) +license: Apache-2.0 +url: https://alertavert.com/statemachine diff --git a/thirdparty/README.md b/thirdparty/README.md new file mode 100644 index 0000000..f607773 --- /dev/null +++ b/thirdparty/README.md @@ -0,0 +1,9 @@ +# Third-Party Libraries + +## common.mk + +This file is a makefile that contains common rules for building and testing +copied from the [`common-utils`](https://github.com/massenz/common-utils) repository. + +It needs to be copied here as it cannot be included in `Makefile` directly from the +original repository. diff --git a/thirdparty/common.mk b/thirdparty/common.mk new file mode 100644 index 0000000..187a33f --- /dev/null +++ b/thirdparty/common.mk @@ -0,0 +1,83 @@ +# Copyright (c) 2024 AlertAvert.com. All rights reserved. +# Created by M. Massenzio, 2022-03-14 + +# ANSI color codes +GREEN=$(shell tput -Txterm setaf 2) +YELLOW=$(shell tput -Txterm setaf 3) +RED=$(shell tput -Txterm setaf 1) +BLUE=$(shell tput -Txterm setaf 6) +RESET=$(shell tput -Txterm sgr0) + +# Go platform management +GOOS ?= $(shell uname -s | tr "[:upper:]" "[:lower:]") +GOMOD := $(shell go list -m) + +UNAME_M := $(shell uname -m) +ifeq ($(UNAME_M),x86_64) + GOARCH = amd64 +else ifeq ($(UNAME_M),aarch64) + GOARCH = arm64 +else ifeq ($(UNAME_M),armv6l) + GOARCH = arm +else ifeq ($(UNAME_M),armv7l) + GOARCH = arm +else ifeq ($(UNAME_M),armv8l) + GOARCH = arm64 +else + $(error Unsupported architecture $(UNAME_M)) +endif + +yq != which yq +ifeq ($(strip $(yq)),) + $(error yq not installed) +endif + +settings != find . -name settings.yaml +ifeq ($(strip $(settings)),) + $(warning $(YELLOW)This makefile requires a settings.yaml file to define appname and version$(RESET)) +else + appname != yq -r .name settings.yaml + version != yq -r .version settings.yaml +endif + +# Versioning +# The `version` is a static value, set in settings.yaml, and ONLY used to tag the release, +# `release` includes the git SHA and will be used to tag the binary and container. +git_commit != git rev-parse --short HEAD +ifndef version + $(error $(RED)version must be defined, use yq and settings.yaml, or define it before including this file$(RESET)) +endif +release := v$(version)-g$(git_commit) + + +# Certificates +certs_dir := ssl-config +ca-csr := $(certs_dir)/ca-csr.json +ca-config := $(certs_dir)/ca-config.json +server-csr := $(certs_dir)/localhost-csr.json + +# Dockerfile +compose := docker/docker-compose.yaml +dockerfile := docker/Dockerfile + +##@ Help +# +# The help target prints out all targets with their descriptions organized +# beneath their categories. +# +# The categories are represented by '##@' and the target descriptions by '##'. +# +# A category is defined if there's a line starting with ##@ , +# that gets pretty-printed as a category. +# A target is defined by a trailing comment starting with ##. +# +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php +# +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) +