# useful paths MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) PROJECT_PATH := $(patsubst %/,%,$(dir $(MKFILE_PATH))) PROJECT_BIN := $(PROJECT_PATH)/bin GO ?= "$(shell which go)" UI_PATH := $(PROJECT_PATH)/clients/ui CSI_PATH := $(PROJECT_PATH)/cmd/csi CONTROLLER_PATH := $(PROJECT_PATH)/cmd/controller # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.29 ENVTEST ?= $(PROJECT_BIN)/setup-envtest # add tools bin directory PATH := $(PROJECT_BIN):$(PATH) # docker executable DOCKER ?= docker # default Dockerfile DOCKERFILE ?= Dockerfile # container registry, default to github container registry IMG_REGISTRY ?= ghcr.io # container image organization IMG_ORG ?= kubeflow # container image version IMG_VERSION ?= main # container image repository IMG_REPO ?= model-registry/server # container image build path BUILD_PATH ?= . # container image ifdef IMG IMG := ${IMG} else ifdef IMG_REGISTRY IMG := ${IMG_REGISTRY}/${IMG_ORG}/${IMG_REPO} else IMG := ${IMG_ORG}/${IMG_REPO} endif # Change Dockerfile path depending on IMG_REPO ifeq ($(IMG_REPO),model-registry/ui) DOCKERFILE := $(UI_PATH)/Dockerfile BUILD_PATH := $(UI_PATH) endif # The BUILD_PATH is still the root ifeq ($(IMG_REPO),model-registry/storage-initializer) DOCKERFILE := $(CSI_PATH)/Dockerfile.csi endif # The BUILD_PATH is still the root ifeq ($(IMG_REPO),model-registry/controller) DOCKERFILE := $(CONTROLLER_PATH)/Dockerfile.controller endif model-registry: build internal/converter/generated/converter.go: internal/converter/*.go ${GOVERTER} gen github.com/kubeflow/model-registry/internal/converter/ .PHONY: gen/converter gen/converter: internal/converter/generated/converter.go api/openapi/model-registry.yaml: api/openapi/src/model-registry.yaml api/openapi/src/lib/*.yaml bin/yq scripts/merge_openapi.sh model-registry.yaml api/openapi/catalog.yaml: api/openapi/src/catalog.yaml api/openapi/src/lib/*.yaml bin/yq scripts/merge_openapi.sh catalog.yaml # validate the openapi schema .PHONY: openapi/validate openapi/validate: bin/openapi-generator-cli bin/yq @scripts/merge_openapi.sh --check model-registry.yaml || (echo "api/openapi/model-registry.yaml is incorrectly formatted. Run 'make api/openapi/model-registry.yaml' to fix it."; exit 1) @scripts/merge_openapi.sh --check catalog.yaml || (echo "$< is incorrectly formatted. Run 'make api/openapi/catalog.yaml' to fix it."; exit 1) $(OPENAPI_GENERATOR) validate -i api/openapi/model-registry.yaml $(OPENAPI_GENERATOR) validate -i api/openapi/catalog.yaml # generate the openapi server implementation .PHONY: gen/openapi-server gen/openapi-server: bin/openapi-generator-cli api/openapi/model-registry.yaml api/openapi/catalog.yaml openapi/validate internal/server/openapi/api_model_registry_service.go make -C catalog $@ internal/server/openapi/api_model_registry_service.go: bin/openapi-generator-cli api/openapi/model-registry.yaml ./scripts/gen_openapi_server.sh # generate the openapi schema model and client .PHONY: gen/openapi gen/openapi: bin/openapi-generator-cli api/openapi/model-registry.yaml api/openapi/catalog.yaml openapi/validate pkg/openapi/client.go make -C catalog $@ pkg/openapi/client.go: bin/openapi-generator-cli api/openapi/model-registry.yaml clean-pkg-openapi ${OPENAPI_GENERATOR} generate \ -i api/openapi/model-registry.yaml -g go -o pkg/openapi --package-name openapi \ --ignore-file-override ./.openapi-generator-ignore --additional-properties=isGoSubmodule=true,enumClassPrefix=true,useOneOfDiscriminatorLookup=true gofmt -w pkg/openapi # Start the MySQL database .PHONY: start/mysql start/mysql: ./scripts/start_mysql_db.sh # Stop the MySQL database .PHONY: stop/mysql stop/mysql: ./scripts/teardown_mysql_db.sh # Start the PostgreSQL database .PHONY: start/postgres start/postgres: ./scripts/start_postgres_db.sh # Stop the PostgreSQL database .PHONY: stop/postgres stop/postgres: ./scripts/teardown_postgres_db.sh # generate the gorm structs for MySQL .PHONY: gen/gorm/mysql gen/gorm/mysql: bin/golang-migrate start/mysql @(trap 'cd $(CURDIR) && $(MAKE) stop/mysql' EXIT; \ $(GOLANG_MIGRATE) -path './internal/datastore/embedmd/mysql/migrations' -database 'mysql://root:root@tcp(localhost:3306)/model-registry' up && \ cd gorm-gen && GOWORK=off go run main.go --db-type mysql --dsn 'root:root@tcp(localhost:3306)/model-registry?charset=utf8mb4&parseTime=True&loc=Local') # generate the gorm structs for PostgreSQL .PHONY: gen/gorm/postgres gen/gorm/postgres: bin/golang-migrate start/postgres @(trap 'cd $(CURDIR) && $(MAKE) stop/postgres' EXIT; \ $(GOLANG_MIGRATE) -path './internal/datastore/embedmd/postgres/migrations' -database 'postgres://postgres:postgres@localhost:5432/model-registry?sslmode=disable' up && \ cd gorm-gen && GOWORK=off go run main.go --db-type postgres --dsn 'postgres://postgres:postgres@localhost:5432/model-registry?sslmode=disable' && \ cd $(CURDIR) && ./scripts/remove_gorm_defaults.sh) # generate the gorm structs (defaults to MySQL for backward compatibility) # Use GORM_DB_TYPE=postgres to generate for PostgreSQL instead .PHONY: gen/gorm gen/gorm: bin/golang-migrate ifeq ($(GORM_DB_TYPE),postgres) $(MAKE) gen/gorm/postgres else $(MAKE) gen/gorm/mysql endif .PHONY: vet vet: @echo "Running go vet on all packages..." @${GO} vet $$(${GO} list ./... | grep -vF github.com/kubeflow/model-registry/internal/db/filter) && \ echo "Checking filter package (parser.go excluded due to participle struct tags)..." && \ cd internal/db/filter && ${GO} build -o /dev/null . 2>&1 | grep -E "vet:|error:" || echo "✓ Filter package builds successfully" .PHONY: clean/csi clean/csi: rm -Rf ./mr-storage-initializer .PHONY: clean-pkg-openapi clean-pkg-openapi: while IFS= read -r file; do rm -f "pkg/openapi/$$file"; done < pkg/openapi/.openapi-generator/FILES make -C catalog $@ .PHONY: clean-internal-server-openapi clean-internal-server-openapi: while IFS= read -r file; do rm -f "internal/server/openapi/$$file"; done < internal/server/openapi/.openapi-generator/FILES make -C catalog $@ .PHONY: clean clean: clean-pkg-openapi clean-internal-server-openapi clean/csi rm -Rf ./model-registry internal/converter/generated/*.go .PHONY: clean/odh clean/odh: rm -Rf ./model-registry bin/envtest: GOBIN=$(PROJECT_BIN) ${GO} install sigs.k8s.io/controller-runtime/tools/setup-envtest@v0.0.0-20240320141353-395cfc7486e6 GOLANGCI_LINT ?= ${PROJECT_BIN}/golangci-lint bin/golangci-lint: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(PROJECT_BIN) v2.0.2 GOVERTER ?= ${PROJECT_BIN}/goverter bin/goverter: GOBIN=$(PROJECT_PATH)/bin ${GO} install github.com/jmattheis/goverter/cmd/goverter@v1.8.1 YQ ?= ${PROJECT_BIN}/yq bin/yq: GOBIN=$(PROJECT_PATH)/bin ${GO} install github.com/mikefarah/yq/v4@v4.45.1 GOLANG_MIGRATE ?= ${PROJECT_BIN}/migrate bin/golang-migrate: GOBIN=$(PROJECT_PATH)/bin ${GO} install -tags 'mysql,postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.18.3 GENQLIENT ?= ${PROJECT_BIN}/genqlient bin/genqlient: GOBIN=$(PROJECT_PATH)/bin ${GO} install github.com/Khan/genqlient@v0.7.0 OPENAPI_GENERATOR ?= ${PROJECT_BIN}/openapi-generator-cli NPM ?= "$(shell which npm)" bin/openapi-generator-cli: ifeq (, $(shell which ${NPM} 2> /dev/null)) @echo "npm is not available please install it to be able to install openapi-generator" exit 1 endif ifeq (, $(shell which ${PROJECT_BIN}/openapi-generator-cli 2> /dev/null)) @{ \ set -e ;\ mkdir -p ${PROJECT_BIN} ;\ mkdir -p ${PROJECT_BIN}/openapi-generator-installation ;\ cd ${PROJECT_BIN} ;\ ${NPM} install -g --prefix ${PROJECT_BIN}/openapi-generator-installation @openapitools/openapi-generator-cli ;\ ln -s openapi-generator-installation/bin/openapi-generator-cli openapi-generator-cli ;\ } endif .PHONY: clean/deps clean/deps: rm -Rf bin/* .PHONY: deps deps: bin/golangci-lint bin/goverter bin/openapi-generator-cli bin/envtest .PHONY: vendor vendor: ${GO} mod vendor .PHONY: update/worksum update/worksum: ${GO} clean --modcache ${GO} mod download # WARNING: DO NOT DELETE THIS TARGET, USED BY Dockerfile!!! .PHONY: build/prepare build/prepare: gen vet lint # WARNING: DO NOT DELETE THIS TARGET, USED BY Dockerfile!!! .PHONY: build/compile build/compile: ${GO} build -buildvcs=false # WARNING: DO NOT EDIT THIS TARGET DIRECTLY!!! # Use build/prepare to add build prerequisites # Use build/compile to add/edit go source compilation # WARNING: Editing this target directly WILL affect the Dockerfile image build!!! .PHONY: build build: build/prepare build/compile .PHONY: build/odh build/odh: vet ${GO} build -buildvcs=false .PHONY: build/prepare/csi build/prepare/csi: build/prepare lint/csi .PHONY: build/compile/csi build/compile/csi: ${GO} build -buildvcs=false -o mr-storage-initializer ${CSI_PATH}/main.go .PHONY: build/csi build/csi: build/prepare/csi build/compile/csi .PHONY: gen gen: deps gen/openapi gen/openapi-server gen/converter ${GO} generate ./... .PHONY: lint lint: bin/golangci-lint ${GOLANGCI_LINT} run main.go --timeout 3m ${GOLANGCI_LINT} run cmd/... internal/... ./pkg/... --timeout 3m .PHONY: lint/csi lint/csi: bin/golangci-lint ${GOLANGCI_LINT} run ${CSI_PATH}/main.go ${GOLANGCI_LINT} run internal/csi/... .PHONY: test test: bin/envtest KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" ${GO} test ./internal/... ./pkg/... .PHONY: test-nocache test-nocache: bin/envtest KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" ${GO} test ./internal/... ./pkg/... -count=1 .PHONY: test-cover test-cover: bin/envtest KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" ${GO} test ./internal/... ./pkg/... -coverprofile=coverage.txt ${GO} tool cover -html=coverage.txt -o coverage.html .PHONY: run/proxy run/proxy: gen ${GO} run main.go proxy --logtostderr=true .PHONY: proxy proxy: build ./model-registry proxy --logtostderr=true # login to docker .PHONY: docker/login docker/login: ifdef IMG_REGISTRY $(DOCKER) login -u "${DOCKER_USER}" -p "${DOCKER_PWD}" "${IMG_REGISTRY}" else $(DOCKER) login -u "${DOCKER_USER}" -p "${DOCKER_PWD}" endif # build docker image .PHONY: image/build image/build: ${DOCKER} build ${BUILD_PATH} -f ${DOCKERFILE} -t ${IMG}:$(IMG_VERSION) $(ARGS) # build docker image using buildx # PLATFORMS defines the target platforms for the model registry image be built to provide support to multiple # architectures. (i.e. make docker-buildx). To use this option you need to: # - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ # - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ # - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail) # To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le .PHONY: image/buildx image/buildx: ifeq ($(DOCKER),docker) # docker uses builder containers - $(DOCKER) buildx rm model-registry-builder $(DOCKER) buildx create --use --name model-registry-builder --platform=$(PLATFORMS) $(DOCKER) buildx build --push --platform=$(PLATFORMS) --tag ${IMG}:$(IMG_VERSION) -f ${DOCKERFILE} . $(DOCKER) buildx rm model-registry-builder else ifeq ($(DOCKER),podman) # podman uses image manifests $(DOCKER) manifest create -a ${IMG} $(DOCKER) buildx build --platform=$(PLATFORMS) --manifest ${IMG}:$(IMG_VERSION) -f ${DOCKERFILE} . $(DOCKER) manifest push ${IMG} $(DOCKER) manifest rm ${IMG} else $(error Unsupported container tool $(DOCKER)) endif # push docker image .PHONY: image/push image/push: ${DOCKER} push ${IMG}:$(IMG_VERSION) all: model-registry ## ------------------------------- ## ## ---- Controller Targets ---- ## ## ------------------------------- ## ##@ Development .PHONY: controller/manifests controller/manifests: bin/controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) rbac:roleName=model-registry-manager-role crd webhook paths="{./cmd/controller/..., ./internal/controller/...}" output:crd:artifacts:config=manifests/options/controller/crd/bases output:rbac:dir=manifests/kustomize/options/controller/rbac .PHONY: controller/generate controller/generate: bin/controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile="./cmd/controller/hack/boilerplate.go.txt" paths="{./cmd/controller/..., ./internal/controller/...}" .PHONY: controller/fmt controller/fmt: ## Run go fmt against code. go fmt ./cmd/controller/... ./internal/controller/... .PHONY: controller/vet controller/vet: ## Run go vet against code. go vet ./cmd/controller/... ./internal/controller/... .PHONY: controller/test controller/test: controller/manifests controller/generate controller/fmt controller/vet bin/envtest ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(PROJECT_BIN) -p path)" go test $$(go list ./internal/controller/... | grep -vF /e2e) -coverprofile cover.out ##@ Build .PHONY: controller/build controller/build: controller/manifests controller/generate controller/fmt controller/vet ## Build manager binary. go build -o bin/manager cmd/controller/main.go .PHONY: controller/run controller/run: controller/manifests controller/generate controller/fmt controller/vet ## Run a controller from your host. go run ./cmd/controller/main.go # If you wish to build the manager image targeting other platforms you can use the --platform flag. # (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. # More info: https://docs.docker.com/develop/develop-images/build_enhancements/ .PHONY: controller/docker-build controller/docker-build: ## Build docker image with the manager. $(DOCKER) build -t ${IMG} -f ./cmd/controller/Dockerfile.controller . .PHONY: controller/docker-push controller/docker-push: ## Push docker image with the manager. $(DOCKER) push ${IMG} # PLATFORMS defines the target platforms for the manager image be built to provide support to multiple # architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: # - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ # - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ # - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail) # To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le .PHONY: controller/docker-buildx controller/docker-buildx: ## Build and push docker image for the manager for cross-platform support # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' ./cmd/controller/Dockerfile.controller > Dockerfile.cross - $(DOCKER) buildx create --name controller-builder $(DOCKER) buildx use controller-builder - $(DOCKER) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . - $(DOCKER) buildx rm controller-builder rm Dockerfile.cross .PHONY: controller/build-installer controller/build-installer: controller/manifests controller/generate bin/kustomize ## Generate a consolidated YAML with CRDs and deployment. mkdir -p dist cd manifests/kustomize/options/controller/manager && $(KUSTOMIZE) edit set image controller=${IMG} $(KUSTOMIZE) build manifests/kustomize/options/controller/default > dist/install.yaml ##@ Deployment ifndef ignore-not-found ignore-not-found = false endif .PHONY: controller/install controller/install: controller/manifests bin/kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build manifests/kustomize/options/controller/crd | $(KUBECTL) apply -f - .PHONY: controller/uninstall controller/uninstall: controller/manifests bin/kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. $(KUSTOMIZE) build manifests/kustomize/options/controller/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - .PHONY: controller/deploy controller/deploy: controller/manifests bin/kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. cd manifests/kustomize/options/controller/manager && $(KUSTOMIZE) edit set image ghcr.io/kubeflow/model-registry/controller=${IMG}:${IMG_VERSION} $(KUSTOMIZE) build manifests/kustomize/options/controller/overlays/base | $(KUBECTL) apply -f - .PHONY: controller/undeploy controller/undeploy: bin/kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. $(KUSTOMIZE) build manifests/kustomize/options/controller/overlays/base | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - ##@ Docker Compose # Auto-detect compose command: prefer podman-compose if available, fallback to docker-compose COMPOSE_CMD ?= $(shell if command -v podman-compose >/dev/null 2>&1; then echo "podman-compose"; elif command -v docker-compose >/dev/null 2>&1; then echo "docker-compose"; else echo "docker compose"; fi) .PHONY: compose/up compose/up: ## Start services using docker-compose.yaml with MySQL $(COMPOSE_CMD) --profile mysql up .PHONY: compose/up/postgres compose/up/postgres: ## Start services using docker-compose.yaml with PostgreSQL DB_TYPE=postgres $(COMPOSE_CMD) --profile postgres up .PHONY: compose/down compose/down: ## Stop services using docker-compose.yaml $(COMPOSE_CMD) down .PHONY: compose/local/up compose/local/up: ## Start services using docker-compose-local.yaml with MySQL (builds from source) $(COMPOSE_CMD) -f docker-compose-local.yaml --profile mysql up .PHONY: compose/local/up/postgres compose/local/up/postgres: ## Start services using docker-compose-local.yaml with PostgreSQL (builds from source) DB_TYPE=postgres $(COMPOSE_CMD) -f docker-compose-local.yaml --profile postgres up .PHONY: compose/local/down compose/local/down: ## Stop services using docker-compose-local.yaml $(COMPOSE_CMD) -f docker-compose-local.yaml down .PHONY: compose/clean compose/clean: ## Remove all Docker Compose volumes and networks $(COMPOSE_CMD) down -v --remove-orphans $(COMPOSE_CMD) -f docker-compose-local.yaml down -v --remove-orphans ##@ Tools KUBECTL ?= kubectl CONTROLLER_GEN ?= $(PROJECT_BIN)/controller-gen KUSTOMIZE ?= $(PROJECT_BIN)/kustomize CONTROLLER_TOOLS_VERSION ?= v0.16.4 KUSTOMIZE_VERSION ?= v5.5.0 .PHONY: bin/kustomize bin/kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. $(KUSTOMIZE): $(PROJECT_BIN) $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION)) .PHONY: bin/controller-gen bin/controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. $(CONTROLLER_GEN): $(PROJECT_BIN) $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION)) # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist # $1 - target path with name of binary # $2 - package url which can be installed # $3 - specific version of package define go-install-tool @[ -f "$(1)-$(3)" ] || { \ set -e; \ package=$(2)@$(3) ;\ echo "Downloading $${package}" ;\ rm -f $(1) || true ;\ GOBIN=$(PROJECT_BIN) go install $${package} ;\ mv $(1) $(1)-$(3) ;\ } ;\ ln -sf $(1)-$(3) $(1) endef