diff --git a/.golangci.yml b/.golangci.yml index b8b0227bd4f6..5574b2e7ff18 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -65,5 +65,3 @@ issues: - sdks - ui - vendor - exclude-files: - - server/static/files.go diff --git a/Makefile b/Makefile index 488ba239c079..2e6156638e79 100644 --- a/Makefile +++ b/Makefile @@ -165,25 +165,14 @@ endef cli: dist/argo ui/dist/app/index.html: $(shell find ui/src -type f && find ui -maxdepth 1 -type f) +ifeq ($(STATIC_FILES),true) # `yarn install` is fast (~2s), so you can call it safely. JOBS=max yarn --cwd ui install # `yarn build` is slow, so we guard it with a up-to-date check. JOBS=max yarn --cwd ui build - -$(GOPATH)/bin/staticfiles: Makefile -# update this in Nix when updating it here -ifneq ($(USE_NIX), true) - go install bou.ke/staticfiles@dd04075 -endif - -ifeq ($(STATIC_FILES),true) -server/static/files.go: $(GOPATH)/bin/staticfiles ui/dist/app/index.html - # Pack UI into a Go file - $(GOPATH)/bin/staticfiles -o server/static/files.go ui/dist/app else -server/static/files.go: - # Building without static files - cp ./server/static/files.go.stub ./server/static/files.go + @mkdir -p ui/dist/app + touch ui/dist/app/index.html endif dist/argo-linux-amd64: GOARGS = GOOS=linux GOARCH=amd64 @@ -198,16 +187,16 @@ dist/argo-windows-amd64: GOARGS = GOOS=windows GOARCH=amd64 dist/argo-windows-%.gz: dist/argo-windows-% gzip --force --keep dist/argo-windows-$*.exe -dist/argo-windows-%: server/static/files.go $(CLI_PKG_FILES) go.sum +dist/argo-windows-%: ui/dist/app/index.html $(CLI_PKG_FILES) go.sum CGO_ENABLED=0 $(GOARGS) go build -v -gcflags '${GCFLAGS}' -ldflags '${LDFLAGS} -extldflags -static' -o $@.exe ./cmd/argo dist/argo-%.gz: dist/argo-% gzip --force --keep dist/argo-$* -dist/argo-%: server/static/files.go $(CLI_PKG_FILES) go.sum +dist/argo-%: ui/dist/app/index.html $(CLI_PKG_FILES) go.sum CGO_ENABLED=0 $(GOARGS) go build -v -gcflags '${GCFLAGS}' -ldflags '${LDFLAGS} -extldflags -static' -o $@ ./cmd/argo -dist/argo: server/static/files.go $(CLI_PKG_FILES) go.sum +dist/argo: ui/dist/app/index.html $(CLI_PKG_FILES) go.sum ifeq ($(shell uname -s),Darwin) # if local, then build fast: use CGO and dynamic-linking go build -v -gcflags '${GCFLAGS}' -ldflags '${LDFLAGS}' -o $@ ./cmd/argo @@ -454,7 +443,7 @@ $(GOPATH)/bin/golangci-lint: Makefile curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b `go env GOPATH`/bin v1.61.0 .PHONY: lint -lint: server/static/files.go $(GOPATH)/bin/golangci-lint +lint: ui/dist/app/index.html $(GOPATH)/bin/golangci-lint rm -Rf v3 vendor # If you're using `woc.wf.Spec` or `woc.execWf.Status` your code probably won't work with WorkflowTemplate. # * Change `woc.wf.Spec` to `woc.execWf.Spec`. @@ -471,7 +460,7 @@ lint: server/static/files.go $(GOPATH)/bin/golangci-lint # for local we have a faster target that prints to stdout, does not use json, and can cache because it has no coverage .PHONY: test -test: server/static/files.go +test: ui/dist/app/index.html go build ./... env KUBECONFIG=/dev/null $(GOTEST) ./... # marker file, based on it's modification time, we know how long ago this target was run @@ -702,11 +691,11 @@ go-diagrams/diagram.dot: ./hack/docs/diagram.go docs/assets/diagram.png: go-diagrams/diagram.dot cd go-diagrams && dot -Tpng diagram.dot -o ../docs/assets/diagram.png -docs/fields.md: api/openapi-spec/swagger.json $(shell find examples -type f) hack/docs/fields.go +docs/fields.md: api/openapi-spec/swagger.json $(shell find examples -type f) ui/dist/app/index.html hack/docs/fields.go env ARGO_SECURE=false ARGO_INSECURE_SKIP_VERIFY=false ARGO_SERVER= ARGO_INSTANCEID= go run ./hack/docs fields # generates several other files -docs/cli/argo.md: $(CLI_PKG_FILES) go.sum server/static/files.go hack/docs/cli.go +docs/cli/argo.md: $(CLI_PKG_FILES) go.sum ui/dist/app/index.html hack/docs/cli.go go run ./hack/docs cli # docs diff --git a/dev/nix/conf.nix b/dev/nix/conf.nix index 1850eb2d0f22..520f6b187434 100644 --- a/dev/nix/conf.nix +++ b/dev/nix/conf.nix @@ -5,7 +5,6 @@ # Even then the buildFlags are not passed into Go, meaning you won't see the correct version info yet. # This is only intended for quick developing at the moment, gradually more functionality will be pushed here. rec { - staticFiles = false; # not acted upon version = "latest"; env = { DEFAULT_REQUEUE_TIME = "1s"; diff --git a/dev/nix/flake.lock b/dev/nix/flake.lock index 532b2c762cac..ba1b2a53ddda 100644 --- a/dev/nix/flake.lock +++ b/dev/nix/flake.lock @@ -1,22 +1,84 @@ { "nodes": { + "cachix": { + "inputs": { + "devenv": "devenv_2", + "flake-compat": [ + "devenv", + "flake-compat" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "pre-commit-hooks": [ + "devenv", + "pre-commit-hooks" + ] + }, + "locked": { + "lastModified": 1712055811, + "narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=", + "owner": "cachix", + "repo": "cachix", + "rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "cachix", + "type": "github" + } + }, "devenv": { "inputs": { - "flake-compat": "flake-compat", + "cachix": "cachix", + "flake-compat": "flake-compat_2", + "nix": "nix_2", + "nixpkgs": "nixpkgs_2", + "pre-commit-hooks": "pre-commit-hooks" + }, + "locked": { + "lastModified": 1719993933, + "narHash": "sha256-6o8OGE40gWCugfvk8mLaqq3BQYq8H35/jV8aJt2egJk=", + "owner": "cachix", + "repo": "devenv", + "rev": "83b295ec9febbc662040ffa8539d23f294af275d", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "devenv_2": { + "inputs": { + "flake-compat": [ + "devenv", + "cachix", + "flake-compat" + ], "nix": "nix", "nixpkgs": "nixpkgs", - "pre-commit-hooks": "pre-commit-hooks" + "poetry2nix": "poetry2nix", + "pre-commit-hooks": [ + "devenv", + "cachix", + "pre-commit-hooks" + ] }, "locked": { - "lastModified": 1682353433, - "narHash": "sha256-pTz7KZ7RlWIP9EiTdUIHzOuozGoga0FIFUjm0rtQP60=", + "lastModified": 1708704632, + "narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=", "owner": "cachix", "repo": "devenv", - "rev": "b454e31b73e1ce987e721e0a7a43044253d6b91a", + "rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196", "type": "github" }, "original": { "owner": "cachix", + "ref": "python-rewrite", "repo": "devenv", "type": "github" } @@ -37,6 +99,22 @@ "type": "github" } }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": [ @@ -44,11 +122,11 @@ ] }, "locked": { - "lastModified": 1680392223, - "narHash": "sha256-n3g7QFr85lDODKt250rkZj2IFS3i4/8HBU2yKHO3tqw=", + "lastModified": 1719994518, + "narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "dcc36e45d054d7bb554c9cdab69093debd91a0b5", + "rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7", "type": "github" }, "original": { @@ -58,12 +136,33 @@ } }, "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", "owner": "numtide", "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -81,11 +180,11 @@ ] }, "locked": { - "lastModified": 1660459072, - "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", "owner": "hercules-ci", "repo": "gitignore.nix", - "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", "type": "github" }, "original": { @@ -94,53 +193,39 @@ "type": "github" } }, - "lowdown-src": { - "flake": false, - "locked": { - "lastModified": 1633514407, - "narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=", - "owner": "kristapsdz", - "repo": "lowdown", - "rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8", - "type": "github" - }, - "original": { - "owner": "kristapsdz", - "repo": "lowdown", - "type": "github" - } - }, "nix": { "inputs": { - "lowdown-src": "lowdown-src", + "flake-compat": "flake-compat", "nixpkgs": [ + "devenv", + "cachix", "devenv", "nixpkgs" ], "nixpkgs-regression": "nixpkgs-regression" }, "locked": { - "lastModified": 1676545802, - "narHash": "sha256-EK4rZ+Hd5hsvXnzSzk2ikhStJnD63odF7SzsQ8CuSPU=", + "lastModified": 1712911606, + "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", "owner": "domenkozar", "repo": "nix", - "rev": "7c91803598ffbcfe4a55c44ac6d49b2cf07a527f", + "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", "type": "github" }, "original": { "owner": "domenkozar", - "ref": "relaxed-flakes", + "ref": "devenv-2.21", "repo": "nix", "type": "github" } }, "nix-filter": { "locked": { - "lastModified": 1681154353, - "narHash": "sha256-MCJ5FHOlbfQRFwN0brqPbCunLEVw05D/3sRVoNVt2tI=", + "lastModified": 1710156097, + "narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=", "owner": "numtide", "repo": "nix-filter", - "rev": "f529f42792ade8e32c4be274af6b6d60857fbee7", + "rev": "3342559a24e85fc164b295c3444e8a139924675b", "type": "github" }, "original": { @@ -149,13 +234,64 @@ "type": "github" } }, + "nix-github-actions": { + "inputs": { + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "poetry2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688870561, + "narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "165b1650b753316aa7f1787f3005a8d2da0f5301", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, + "nix_2": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-regression": "nixpkgs-regression_2" + }, + "locked": { + "lastModified": 1712911606, + "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", + "owner": "domenkozar", + "repo": "nix", + "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "devenv-2.21", + "repo": "nix", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1678875422, - "narHash": "sha256-T3o6NcQPwXjxJMn2shz86Chch4ljXgZn746c2caGxd8=", + "lastModified": 1692808169, + "narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "126f49a01de5b7e35a43fd43f891ecf6d3a51459", + "rev": "9201b5ff357e781bf014d0330d18555695df7ba8", "type": "github" }, "original": { @@ -181,29 +317,61 @@ "type": "github" } }, + "nixpkgs-regression_2": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, "nixpkgs-stable": { "locked": { - "lastModified": 1673800717, - "narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=", + "lastModified": 1710695816, + "narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f", + "rev": "614b4613980a522ba49f0d194531beddbb7220d3", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-22.11", + "ref": "nixos-23.11", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1716408587, - "narHash": "sha256-el71IUaQdEmntmd51GBpkJs/Hqh6S4dmfmUGP8GQaME=", + "lastModified": 1713361204, + "narHash": "sha256-TA6EDunWTkc5FvDCqU3W2T3SFn0gRZqh6D/hJnM02MM=", + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "285676e87ad9f0ca23d8714a6ab61e7e027020c6", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1719838683, + "narHash": "sha256-Zw9rQjHz1ilNIimEXFeVa1ERNRBF8DoXDhLAZq5B4pE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1a7abfa62e8a36f7f2dbe463722ed9ea31be5e43", + "rev": "d032c1a6dfad4eedec7e35e91986becc699d7d69", "type": "github" }, "original": { @@ -213,13 +381,13 @@ "type": "github" } }, - "nixpkgs_3": { + "nixpkgs_4": { "locked": { - "lastModified": 1680945546, - "narHash": "sha256-8FuaH5t/aVi/pR1XxnF0qi4WwMYC+YxlfdsA0V+TEuQ=", + "lastModified": 1719690277, + "narHash": "sha256-0xSej1g7eP2kaUF+JQp8jdyNmpmCJKRpO12mKl/36Kc=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d9f759f2ea8d265d974a6e1259bd510ac5844c5d", + "rev": "2741b4b489b55df32afac57bc4bfd220e8bf617e", "type": "github" }, "original": { @@ -229,13 +397,38 @@ "type": "github" } }, + "poetry2nix": { + "inputs": { + "flake-utils": "flake-utils", + "nix-github-actions": "nix-github-actions", + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1692876271, + "narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=", + "owner": "nix-community", + "repo": "poetry2nix", + "rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "poetry2nix", + "type": "github" + } + }, "pre-commit-hooks": { "inputs": { "flake-compat": [ "devenv", "flake-compat" ], - "flake-utils": "flake-utils", + "flake-utils": "flake-utils_2", "gitignore": "gitignore", "nixpkgs": [ "devenv", @@ -244,11 +437,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1678376203, - "narHash": "sha256-3tyYGyC8h7fBwncLZy5nCUjTJPrHbmNwp47LlNLOHSM=", + "lastModified": 1713775815, + "narHash": "sha256-Wu9cdYTnGQQwtT20QQMg7jzkANKQjwBD9iccfGKkfls=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "1a20b9708962096ec2481eeb2ddca29ed747770a", + "rev": "2ac4dcbf55ed43f3be0bae15e181f08a57af24a4", "type": "github" }, "original": { @@ -262,20 +455,50 @@ "devenv": "devenv", "flake-parts": "flake-parts", "nix-filter": "nix-filter", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs_3", "treefmt-nix": "treefmt-nix" } }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "treefmt-nix": { "inputs": { - "nixpkgs": "nixpkgs_3" + "nixpkgs": "nixpkgs_4" }, "locked": { - "lastModified": 1681486253, - "narHash": "sha256-EjiQZvXQH9tUPCyLC6lQpfGnoq4+kI9v59bDJWPicYo=", + "lastModified": 1719887753, + "narHash": "sha256-p0B2r98UtZzRDM5miGRafL4h7TwGRC4DII+XXHDHqek=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "b25d1a3c2c7554d0462ab1dfddf2f13128638b90", + "rev": "bdb6355009562d8f9313d9460c0d3860f525bc6c", "type": "github" }, "original": { diff --git a/dev/nix/flake.nix b/dev/nix/flake.nix index eb3b848ad086..9e297b8dd1ce 100644 --- a/dev/nix/flake.nix +++ b/dev/nix/flake.nix @@ -307,17 +307,6 @@ doCheck = false; }; - staticfiles = pkgs.buildGoPackage rec { - name = "staticfiles"; - src = pkgs.fetchFromGitHub { - owner = "bouk"; - repo = "staticfiles"; - rev = "827d7f6389cd410d0aa3f3d472a4838557bf53dd"; - sha256 = "0xarhmsqypl8036w96ssdzjv3k098p2d4mkmw5f6hkp1m3j67j61"; - }; - - goPackagePath = "bou.ke/staticfiles"; - }; default = config.packages.${package.name}; }; @@ -338,7 +327,6 @@ config.packages.k8sio-tools config.packages.goreman config.packages.stern - config.packages.staticfiles config.packages.${package.name} nodePackages.shell.nodeDependencies gopls @@ -368,7 +356,6 @@ config.packages.k8sio-tools config.packages.goreman config.packages.stern - config.packages.staticfiles config.packages.${package.name} nodePackages.shell.nodeDependencies gopls diff --git a/server/apiserver/argoserver.go b/server/apiserver/argoserver.go index 390aaa3b371b..1369c384307a 100644 --- a/server/apiserver/argoserver.go +++ b/server/apiserver/argoserver.go @@ -58,6 +58,7 @@ import ( "github.com/argoproj/argo-workflows/v3/server/workflow/store" "github.com/argoproj/argo-workflows/v3/server/workflowarchive" "github.com/argoproj/argo-workflows/v3/server/workflowtemplate" + "github.com/argoproj/argo-workflows/v3/ui" grpcutil "github.com/argoproj/argo-workflows/v3/util/grpc" "github.com/argoproj/argo-workflows/v3/util/instanceid" "github.com/argoproj/argo-workflows/v3/util/json" @@ -435,7 +436,7 @@ func (as *argoServer) newHTTPServer(ctx context.Context, port int, artifactServe }) // we only enable HTST if we are secure mode, otherwise you would never be able access the UI - mux.HandleFunc("/", static.NewFilesServer(as.baseHRef, as.tlsConfig != nil && as.hsts, as.xframeOptions, as.accessControlAllowOrigin).ServerFiles) + mux.HandleFunc("/", static.NewFilesServer(as.baseHRef, as.tlsConfig != nil && as.hsts, as.xframeOptions, as.accessControlAllowOrigin, ui.Embedded).ServerFiles) return &httpServer } diff --git a/server/static/files.go b/server/static/files.go deleted file mode 100644 index 7179551ae2c9..000000000000 --- a/server/static/files.go +++ /dev/null @@ -1,8 +0,0 @@ -// File built without static files -package static - -import "net/http" - -func ServeHTTP(http.ResponseWriter, *http.Request) {} - -func Hash(string) string { return "" } diff --git a/server/static/files.go.stub b/server/static/files.go.stub deleted file mode 100644 index 7179551ae2c9..000000000000 --- a/server/static/files.go.stub +++ /dev/null @@ -1,8 +0,0 @@ -// File built without static files -package static - -import "net/http" - -func ServeHTTP(http.ResponseWriter, *http.Request) {} - -func Hash(string) string { return "" } diff --git a/server/static/response-rewriter.go b/server/static/response-rewriter.go deleted file mode 100644 index b824143ff7a7..000000000000 --- a/server/static/response-rewriter.go +++ /dev/null @@ -1,20 +0,0 @@ -package static - -import ( - "bytes" - "net/http" - "strconv" -) - -type responseRewriter struct { - http.ResponseWriter - old []byte - new []byte -} - -func (w *responseRewriter) Write(a []byte) (int, error) { - b := bytes.Replace(a, w.old, w.new, 1) - // status code and headers are printed out when we write data - w.Header().Set("Content-Length", strconv.Itoa(len(b))) - return w.ResponseWriter.Write(b) -} diff --git a/server/static/static.go b/server/static/static.go index f2df6d60b6f2..53daf27a9528 100644 --- a/server/static/static.go +++ b/server/static/static.go @@ -1,9 +1,16 @@ package static import ( + "embed" "fmt" "net/http" + "regexp" "strings" + "time" + + "github.com/argoproj/argo-workflows/v3" + "github.com/argoproj/argo-workflows/v3/ui" + "github.com/argoproj/argo-workflows/v3/util/io" ) type FilesServer struct { @@ -11,23 +18,16 @@ type FilesServer struct { hsts bool xframeOpts string corsAllowOrigin string + staticAssets embed.FS } -func NewFilesServer(baseHRef string, hsts bool, xframeOpts string, corsAllowOrigin string) *FilesServer { - return &FilesServer{baseHRef, hsts, xframeOpts, corsAllowOrigin} +var baseHRefRegex = regexp.MustCompile(``) + +func NewFilesServer(baseHRef string, hsts bool, xframeOpts string, corsAllowOrigin string, staticAssets embed.FS) *FilesServer { + return &FilesServer{baseHRef, hsts, xframeOpts, corsAllowOrigin, staticAssets} } func (s *FilesServer) ServerFiles(w http.ResponseWriter, r *http.Request) { - // If there is no stored static file, we'll redirect to the js app - if Hash(strings.TrimLeft(r.URL.Path, "/")) == "" { - r.URL.Path = "index.html" - } - - if r.URL.Path == "index.html" { - // hack to prevent ServerHTTP from giving us gzipped content which we can do our search-and-replace on - r.Header.Del("Accept-Encoding") - w = &responseRewriter{ResponseWriter: w, old: []byte(``), new: []byte(fmt.Sprintf(``, s.baseHRef))} - } if s.xframeOpts != "" { w.Header().Set("X-Frame-Options", s.xframeOpts) @@ -50,6 +50,49 @@ func (s *FilesServer) ServerFiles(w http.ResponseWriter, r *http.Request) { w.Header().Set("Strict-Transport-Security", "max-age=31536000") } - // in my IDE (IntelliJ) the next line is red for some reason - but this is fine - ServeHTTP(w, r) + if r.URL.Path == "/" || !s.uiAssetExists(r.URL.Path) { + data, err := s.getIndexData() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + modTime, err := time.Parse(argo.GetVersion().BuildDate, time.RFC3339) + if err != nil { + modTime = time.Now() + } + http.ServeContent(w, r, "index.html", modTime, io.NewByteReadSeeker(data)) + } else { + staticFS := io.NewSubDirFS(ui.EMBED_PATH, s.staticAssets) + http.FileServer(http.FS(staticFS)).ServeHTTP(w, r) + } +} + +func (s *FilesServer) getIndexData() ([]byte, error) { + data, err := s.staticAssets.ReadFile(ui.EMBED_PATH + "/index.html") + if err != nil { + return data, err + } + if s.baseHRef != "/" && s.baseHRef != "" { + data = []byte(replaceBaseHRef(string(data), fmt.Sprintf(``, strings.Trim(s.baseHRef, "/")))) + } + + return data, nil +} + +func (s *FilesServer) uiAssetExists(filename string) bool { + f, err := s.staticAssets.Open(ui.EMBED_PATH + filename) + if err != nil { + return false + } + defer f.Close() + stat, err := f.Stat() + if err != nil { + return false + } + return !stat.IsDir() +} + +func replaceBaseHRef(data string, replaceWith string) string { + return baseHRefRegex.ReplaceAllString(data, replaceWith) } diff --git a/server/static/static_test.go b/server/static/static_test.go new file mode 100644 index 000000000000..e9682dbfe2c3 --- /dev/null +++ b/server/static/static_test.go @@ -0,0 +1,95 @@ +package static + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReplaceBaseHRef(t *testing.T) { + testCases := []struct { + name string + data string + expected string + replaceWith string + }{ + { + name: "non-root basepath", + data: ` + + + + Argo + + + + + + + + +
+ +`, + expected: ` + + + + Argo + + + + + + + + +
+ +`, + replaceWith: ``, + }, + { + name: "root basepath", + data: ` + + + + Argo + + + + + + + + +
+ +`, + expected: ` + + + + Argo + + + + + + + + +
+ +`, + replaceWith: ``, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + result := replaceBaseHRef(testCase.data, testCase.replaceWith) + assert.Equal(t, testCase.expected, result) + }) + } +} diff --git a/ui/embed.go b/ui/embed.go new file mode 100644 index 000000000000..5c9792fa48d7 --- /dev/null +++ b/ui/embed.go @@ -0,0 +1,10 @@ +package ui + +import "embed" + +const EMBED_PATH = "dist/app" + +// Embedded contains embedded UI resources +// +//go:embed dist/app +var Embedded embed.FS diff --git a/util/io/bytereadseeker.go b/util/io/bytereadseeker.go new file mode 100644 index 000000000000..43c246c2eced --- /dev/null +++ b/util/io/bytereadseeker.go @@ -0,0 +1,38 @@ +package io + +import ( + "io" + "io/fs" +) + +func NewByteReadSeeker(data []byte) *byteReadSeeker { + return &byteReadSeeker{data: data} +} + +type byteReadSeeker struct { + data []byte + offset int64 +} + +func (f *byteReadSeeker) Read(b []byte) (int, error) { + if f.offset >= int64(len(f.data)) { + return 0, io.EOF + } + n := copy(b, f.data[f.offset:]) + f.offset += int64(n) + return n, nil +} + +func (f *byteReadSeeker) Seek(offset int64, whence int) (int64, error) { + switch whence { + case 1: + offset += f.offset + case 2: + offset += int64(len(f.data)) + } + if offset < 0 || offset > int64(len(f.data)) { + return 0, &fs.PathError{Op: "seek", Err: fs.ErrInvalid} + } + f.offset = offset + return offset, nil +} diff --git a/util/io/bytereadseeker_test.go b/util/io/bytereadseeker_test.go new file mode 100644 index 000000000000..d3496bcb81c6 --- /dev/null +++ b/util/io/bytereadseeker_test.go @@ -0,0 +1,72 @@ +package io + +import ( + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestByteReadSeeker_Read(t *testing.T) { + inString := "hello world" + reader := NewByteReadSeeker([]byte(inString)) + var bytes = make([]byte, 11) + n, err := reader.Read(bytes) + require.NoError(t, err) + assert.Equal(t, len(inString), n) + assert.Equal(t, inString, string(bytes)) + _, err = reader.Read(bytes) + assert.ErrorIs(t, err, io.EOF) +} + +func TestByteReadSeeker_Seek_Start(t *testing.T) { + inString := "hello world" + reader := NewByteReadSeeker([]byte(inString)) + offset, err := reader.Seek(6, io.SeekStart) + require.NoError(t, err) + assert.Equal(t, int64(6), offset) + var bytes = make([]byte, 5) + n, err := reader.Read(bytes) + require.NoError(t, err) + assert.Equal(t, 5, n) + assert.Equal(t, "world", string(bytes)) +} + +func TestByteReadSeeker_Seek_Current(t *testing.T) { + inString := "hello world" + reader := NewByteReadSeeker([]byte(inString)) + offset, err := reader.Seek(3, io.SeekCurrent) + require.NoError(t, err) + assert.Equal(t, int64(3), offset) + offset, err = reader.Seek(3, io.SeekCurrent) + require.NoError(t, err) + assert.Equal(t, int64(6), offset) + var bytes = make([]byte, 5) + n, err := reader.Read(bytes) + require.NoError(t, err) + assert.Equal(t, 5, n) + assert.Equal(t, "world", string(bytes)) +} + +func TestByteReadSeeker_Seek_End(t *testing.T) { + inString := "hello world" + reader := NewByteReadSeeker([]byte(inString)) + offset, err := reader.Seek(-5, io.SeekEnd) + require.NoError(t, err) + assert.Equal(t, int64(6), offset) + var bytes = make([]byte, 5) + n, err := reader.Read(bytes) + require.NoError(t, err) + assert.Equal(t, 5, n) + assert.Equal(t, "world", string(bytes)) +} + +func TestByteReadSeeker_Seek_OutOfBounds(t *testing.T) { + inString := "hello world" + reader := NewByteReadSeeker([]byte(inString)) + _, err := reader.Seek(12, io.SeekStart) + assert.Error(t, err) + _, err = reader.Seek(-1, io.SeekStart) + assert.Error(t, err) +} diff --git a/util/io/subdirfs.go b/util/io/subdirfs.go new file mode 100644 index 000000000000..93b0ab7a6e14 --- /dev/null +++ b/util/io/subdirfs.go @@ -0,0 +1,20 @@ +package io + +import ( + "io/fs" + "path/filepath" +) + +type subDirFs struct { + dir string + fs fs.FS +} + +func (s subDirFs) Open(name string) (fs.File, error) { + return s.fs.Open(filepath.Join(s.dir, name)) +} + +// NewSubDirFS returns file system that represents sub-directory in a wrapped file system +func NewSubDirFS(dir string, fs fs.FS) *subDirFs { + return &subDirFs{dir: dir, fs: fs} +}