Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Custom Styles feature is broken #7067

Merged
merged 1 commit into from
Aug 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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}
}