Skip to content

Commit

Permalink
fix: Custom Styles feature is broken (#7067)
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
  • Loading branch information
Alexander Matyushentsev committed Aug 24, 2021
1 parent a894d4b commit 1d3d03f
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 50 deletions.
6 changes: 3 additions & 3 deletions cmd/argocd-server/commands/argocd_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func NewCommand() *cobra.Command {
frameOptions string
repoServerPlaintext bool
repoServerStrictTLS bool
staticAssetsDir string
)
var command = &cobra.Command{
Use: cliName,
Expand Down Expand Up @@ -139,6 +140,7 @@ func NewCommand() *cobra.Command {
Cache: cache,
XFrameOptions: frameOptions,
RedisClient: redisClient,
StaticAssetsDir: staticAssetsDir,
}

stats.RegisterStackDumper()
Expand All @@ -157,9 +159,7 @@ func NewCommand() *cobra.Command {

clientConfig = cli.AddKubectlFlagsToCmd(command)
command.Flags().BoolVar(&insecure, "insecure", env.ParseBoolFromEnv("ARGOCD_SERVER_INSECURE", false), "Run server without TLS")
var staticAssetsDir string
command.Flags().StringVar(&staticAssetsDir, "staticassets", "", "Static assets directory path")
_ = command.Flags().MarkDeprecated("staticassets", "The --staticassets flag is not longer supported. Static assets are embedded into binary.")
command.Flags().StringVar(&staticAssetsDir, "staticassets", env.StringFromEnv("ARGOCD_SERVER_STATIC_ASSETS", "/shared/app"), "Directory path that contains additional static assets")
command.Flags().StringVar(&baseHRef, "basehref", env.StringFromEnv("ARGOCD_SERVER_BASEHREF", "/"), "Value for base href in index.html. Used if Argo CD is running behind reverse proxy under subpath different from /")
command.Flags().StringVar(&rootPath, "rootpath", env.StringFromEnv("ARGOCD_SERVER_ROOTPATH", ""), "Used if Argo CD is running behind reverse proxy under subpath different from /")
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", env.StringFromEnv("ARGOCD_SERVER_LOGFORMAT", "text"), "Set the logging format. One of: text|json")
Expand Down
2 changes: 2 additions & 0 deletions docs/operator-manual/argocd-cmd-params-cm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ data:
server.basehref: "/"
# Used if Argo CD is running behind reverse proxy under subpath different from /
server.rootpath: "/"
# Directory path that contains additional static assets
server.staticassets: "/shared/app"

# Set the logging format. One of: text|json (default "text")
server.log.format: "text"
Expand Down
5 changes: 3 additions & 2 deletions docs/operator-manual/custom-styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ spec:
name: styles
```

Note that the CSS file should be mounted within a subdirectory of the existing "/shared/app" directory
Note that the CSS file should be mounted within a subdirectory of the "/shared/app" directory
(e.g. "/shared/app/custom"). Otherwise, the file will likely fail to be imported by the browser with an
"incorrect MIME type" error.
"incorrect MIME type" error. The subdirectory can be changed using `server.staticassets` key of the
[argocd-cmd-params-cm.yaml](./argocd-cmd-params-cm.yaml) ConfigMap.

## Developing Style Overlays
The styles specified in the injected CSS file should be specific to components and classes defined in [argo-ui](https://github.com/argoproj/argo-ui).
Expand Down
1 change: 1 addition & 0 deletions docs/operator-manual/server-commands/argocd-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ argocd-server [flags]
--sentinel stringArray Redis sentinel hostname and port (e.g. argocd-redis-ha-announce-0:6379).
--sentinelmaster string Redis sentinel master group name. (default "master")
--server string The address and port of the Kubernetes API server
--staticassets string Directory path that contains additional static assets (default "/shared/app")
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
--tlsciphers string The list of acceptable ciphers to be used when establishing TLS connections. Use 'list' to list available ciphers. (default "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_GCM_SHA384")
--tlsmaxversion string The maximum SSL/TLS version that is acceptable (one of: 1.0|1.1|1.2|1.3) (default "1.3")
Expand Down
6 changes: 6 additions & 0 deletions manifests/base/server/argocd-server-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ spec:
name: argocd-cmd-params-cm
key: server.login.attempts.expiration
optional: true
- name: ARGOCD_SERVER_STATIC_ASSETS
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: server.staticassets
optional: true
- name: ARGOCD_APP_STATE_CACHE_EXPIRATION
valueFrom:
configMapKeyRef:
Expand Down
6 changes: 6 additions & 0 deletions manifests/ha/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4114,6 +4114,12 @@ spec:
key: server.login.attempts.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_SERVER_STATIC_ASSETS
valueFrom:
configMapKeyRef:
key: server.staticassets
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APP_STATE_CACHE_EXPIRATION
valueFrom:
configMapKeyRef:
Expand Down
6 changes: 6 additions & 0 deletions manifests/ha/namespace-install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1501,6 +1501,12 @@ spec:
key: server.login.attempts.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_SERVER_STATIC_ASSETS
valueFrom:
configMapKeyRef:
key: server.staticassets
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APP_STATE_CACHE_EXPIRATION
valueFrom:
configMapKeyRef:
Expand Down
6 changes: 6 additions & 0 deletions manifests/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3439,6 +3439,12 @@ spec:
key: server.login.attempts.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_SERVER_STATIC_ASSETS
valueFrom:
configMapKeyRef:
key: server.staticassets
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APP_STATE_CACHE_EXPIRATION
valueFrom:
configMapKeyRef:
Expand Down
6 changes: 6 additions & 0 deletions manifests/namespace-install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,12 @@ spec:
key: server.login.attempts.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_SERVER_STATIC_ASSETS
valueFrom:
configMapKeyRef:
key: server.staticassets
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APP_STATE_CACHE_EXPIRATION
valueFrom:
configMapKeyRef:
Expand Down
58 changes: 13 additions & 45 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import (
"context"
"crypto/tls"
"fmt"
goio "io"
"io/fs"
"math"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
gosync "sync"
Expand Down Expand Up @@ -134,7 +132,6 @@ var (
maxConcurrentLoginRequestsCount = 50
replicasCount = 1
enableGRPCTimeHistogram = true
staticAssets = http.FS(&subDirFs{dir: "dist/app", fs: ui.Embedded})
)

func init() {
Expand Down Expand Up @@ -167,12 +164,14 @@ type ArgoCDServer struct {
indexDataInit gosync.Once
indexData []byte
indexDataErr error
staticAssets http.FileSystem
}

type ArgoCDServerOpts struct {
DisableAuth bool
EnableGZip bool
Insecure bool
StaticAssetsDir string
ListenPort int
MetricsPort int
Namespace string
Expand Down Expand Up @@ -236,6 +235,11 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
policyEnf := rbacpolicy.NewRBACPolicyEnforcer(enf, projLister)
enf.SetClaimsEnforcerFunc(policyEnf.EnforceClaims)

var staticFS fs.FS = io.NewSubDirFS("dist/app", ui.Embedded)
if opts.StaticAssetsDir != "" {
staticFS = io.NewComposableFS(staticFS, os.DirFS(opts.StaticAssetsDir))
}

return &ArgoCDServer{
ArgoCDServerOpts: opts,
log: log.NewEntry(log.StandardLogger()),
Expand All @@ -248,6 +252,7 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
appLister: appLister,
policyEnforcer: policyEnf,
userStateStorage: userStateStorage,
staticAssets: http.FS(staticFS),
}
}

Expand Down Expand Up @@ -844,8 +849,8 @@ func (s *ArgoCDServer) getIndexData() ([]byte, error) {
return s.indexData, s.indexDataErr
}

func uiAssetExists(filename string) bool {
f, err := staticAssets.Open(strings.Trim(filename, "/"))
func (server *ArgoCDServer) uiAssetExists(filename string) bool {
f, err := server.staticAssets.Open(strings.Trim(filename, "/"))
if err != nil {
return false
}
Expand All @@ -868,7 +873,7 @@ func (server *ArgoCDServer) newStaticAssetsHandler() func(http.ResponseWriter, *
}
}

fileRequest := r.URL.Path != "/index.html" && uiAssetExists(r.URL.Path)
fileRequest := r.URL.Path != "/index.html" && server.uiAssetExists(r.URL.Path)

// Set X-Frame-Options according to configuration
if server.XFrameOptions != "" {
Expand All @@ -891,50 +896,13 @@ func (server *ArgoCDServer) newStaticAssetsHandler() func(http.ResponseWriter, *
if err != nil {
modTime = time.Now()
}
http.ServeContent(w, r, "index.html", modTime, byteReadSeeker{data: data})
http.ServeContent(w, r, "index.html", modTime, io.NewByteReadSeeker(data))
} else {
http.FileServer(staticAssets).ServeHTTP(w, r)
http.FileServer(server.staticAssets).ServeHTTP(w, r)
}
}
}

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))
}

type byteReadSeeker struct {
data []byte
offset int64
}

func (f byteReadSeeker) Read(b []byte) (int, error) {
if f.offset >= int64(len(f.data)) {
return 0, goio.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
}

type registerFunc func(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) error

// mustRegisterGWHandler is a convenience function to register a gateway handler
Expand Down
38 changes: 38 additions & 0 deletions util/io/bytereadseeker.go
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 23 additions & 0 deletions util/io/componsablefs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io

import "io/fs"

type composableFS struct {
innerFS []fs.FS
}

// NewComposableFS creates files system that attempts reading file from multiple wrapped file systems
func NewComposableFS(innerFS ...fs.FS) *composableFS {
return &composableFS{innerFS: innerFS}
}

// Open attempts open file in wrapped file systems and returns first successful
func (c composableFS) Open(name string) (f fs.File, err error) {
for i := range c.innerFS {
f, err = c.innerFS[i].Open(name)
if err == nil {
break
}
}
return
}
20 changes: 20 additions & 0 deletions util/io/subdirfs.go
Original file line number Diff line number Diff line change
@@ -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}
}

0 comments on commit 1d3d03f

Please sign in to comment.