diff --git a/cmd/argocd-server/commands/root.go b/cmd/argocd-server/commands/root.go index 7169b0382dbb5..ffd20da73b457 100644 --- a/cmd/argocd-server/commands/root.go +++ b/cmd/argocd-server/commands/root.go @@ -14,9 +14,10 @@ import ( // NewCommand returns a new instance of an argocd command func NewCommand() *cobra.Command { var ( - logLevel string - configMap string - kubeConfig string + logLevel string + configMap string + kubeConfig string + staticAssetsDir string ) var command = &cobra.Command{ Use: cliName, @@ -32,12 +33,13 @@ func NewCommand() *cobra.Command { kubeclientset := kubernetes.NewForConfigOrDie(config) appclientset := appclientset.NewForConfigOrDie(config) - argocd := server.NewServer(kubeclientset, appclientset) + argocd := server.NewServer(kubeclientset, appclientset, staticAssetsDir) argocd.Run() }, } command.Flags().StringVar(&kubeConfig, "kubeconfig", "", "Kubernetes config (used when running outside of cluster)") + command.Flags().StringVar(&staticAssetsDir, "staticassets", "", "Static assets directory path") command.Flags().StringVar(&configMap, "configmap", defaultArgoCDConfigMap, "Name of K8s configmap to retrieve argocd configuration") command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error") command.AddCommand(cli.NewVersionCmd(cliName)) diff --git a/cmd/argocd/commands/install.go b/cmd/argocd/commands/install.go index d612541d62236..3841c4bf952e8 100644 --- a/cmd/argocd/commands/install.go +++ b/cmd/argocd/commands/install.go @@ -19,6 +19,8 @@ var ( // These are the default image names which `argo install` uses during install DefaultControllerImage = imageNamespace + "/argocd-application-controller:" + imageTag + DefaultUiImage = imageNamespace + "/argocd-ui:" + imageTag + DefaultServerImage = imageNamespace + "/argocd-server:" + imageTag ) // NewInstallCommand returns a new instance of `argocd install` command @@ -45,6 +47,9 @@ func NewInstallCommand() *cobra.Command { command.Flags().StringVar(&installParams.Namespace, "install-namespace", common.DefaultControllerNamespace, "install into a specific Namespace") command.Flags().StringVar(&installParams.ControllerName, "controller-name", common.DefaultControllerDeploymentName, "name of controller deployment") command.Flags().StringVar(&installParams.ControllerImage, "controller-image", DefaultControllerImage, "use a specified controller image") + command.Flags().StringVar(&installParams.ServerName, "server-name", common.DefaultServerDeploymentName, "name of api server deployment") + command.Flags().StringVar(&installParams.ServerImage, "server-image", DefaultServerImage, "use a specified api server image") + command.Flags().StringVar(&installParams.UiImage, "ui-image", DefaultUiImage, "use a specified ui image") command.Flags().StringVar(&installParams.ServiceAccount, "service-account", "", "use a specified service account for the workflow-controller deployment") clientConfig = addKubectlFlagsToCmd(command) return command diff --git a/common/common.go b/common/common.go index d59bc1108545c..e6d6c3f05f5aa 100644 --- a/common/common.go +++ b/common/common.go @@ -16,6 +16,9 @@ const ( // DefaultControllerDeploymentName is the default deployment name of the application controller DefaultControllerDeploymentName = "application-controller" + // DefaultServerDeploymentName is the default deployment name of the api server + DefaultServerDeploymentName = "argocd-server" + // DefaultControllerNamespace is the default namespace where the application controller is installed DefaultControllerNamespace = "kube-system" ) diff --git a/common/installer.go b/common/installer.go index 2061e3dfe10c9..cfba4f371a2ff 100644 --- a/common/installer.go +++ b/common/installer.go @@ -22,6 +22,10 @@ import ( "k8s.io/client-go/rest" ) +const ( + StaticFilesVolumeName = "static-files" +) + // InstallParameters has all the required parameters for installing ArgoCD. type InstallParameters struct { Upgrade bool @@ -29,8 +33,11 @@ type InstallParameters struct { Namespace string ControllerName string ControllerImage string + ServerName string + UiImage string + ServerImage string ServiceAccount string - SkipController bool + CrdOnly bool } // Installer allows to install ArgoCD resources. @@ -42,8 +49,9 @@ type Installer struct { // Install performs installation func (installer *Installer) Install(parameters InstallParameters) { installer.installAppCRD(parameters.DryRun) - if !parameters.SkipController { + if !parameters.CrdOnly { installer.installController(parameters) + installer.installServer(parameters) } } @@ -143,9 +151,10 @@ func (installer *Installer) installController(args InstallParameters) { ServiceAccountName: args.ServiceAccount, Containers: []apiv1.Container{ { - Name: args.ControllerName, - Image: args.ControllerImage, - Command: []string{"argocd-application-controller"}, + Name: args.ControllerName, + Image: args.ControllerImage, + ImagePullPolicy: apiv1.PullIfNotPresent, + Command: []string{"/argocd-application-controller"}, }, }, }, @@ -155,6 +164,71 @@ func (installer *Installer) installController(args InstallParameters) { installer.createDeploymentHelper(&controllerDeployment, args) } +func (installer *Installer) installServer(args InstallParameters) { + serverDeployment := appsv1beta2.Deployment{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "apps/v1beta2", + Kind: "Deployment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: args.ServerName, + Namespace: args.Namespace, + }, + Spec: appsv1beta2.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": args.ServerName, + }, + }, + Template: apiv1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app": args.ServerName, + }, + }, + Spec: apiv1.PodSpec{ + ServiceAccountName: args.ServiceAccount, + Containers: []apiv1.Container{ + { + Name: args.ServerName, + Image: args.ServerImage, + Command: []string{"/argocd-server", "--staticassets", "/shared/app"}, + ImagePullPolicy: apiv1.PullIfNotPresent, + VolumeMounts: []apiv1.VolumeMount{ + { + Name: StaticFilesVolumeName, + MountPath: "/shared", + }, + }, + }, + { + Name: args.ServerName + "-ui", + Image: args.UiImage, + ImagePullPolicy: apiv1.PullIfNotPresent, + Command: []string{"sh", "-c", "cp -r /app /shared && tail -f /dev/null"}, + VolumeMounts: []apiv1.VolumeMount{ + { + Name: StaticFilesVolumeName, + MountPath: "/shared", + }, + }, + }, + }, + Volumes: []apiv1.Volume{ + { + Name: StaticFilesVolumeName, + VolumeSource: apiv1.VolumeSource{ + EmptyDir: &apiv1.EmptyDirVolumeSource{}, + }, + }, + }, + }, + }, + }, + } + installer.createDeploymentHelper(&serverDeployment, args) +} + // createDeploymentHelper is helper to create or update an existing deployment (if --upgrade was supplied) func (installer *Installer) createDeploymentHelper(deployment *appsv1beta2.Deployment, args InstallParameters) { depClient := installer.clientset.AppsV1beta2().Deployments(args.Namespace) diff --git a/server/server.go b/server/server.go index 31a2f5fe569c8..d42069297fa7b 100644 --- a/server/server.go +++ b/server/server.go @@ -6,6 +6,8 @@ import ( "net" "net/http" + "strings" + argocd "github.com/argoproj/argo-cd" appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned" "github.com/argoproj/argo-cd/server/application" @@ -33,19 +35,21 @@ var ( // ArgoCDServer is the API server for ArgoCD type ArgoCDServer struct { - ns string - kubeclientset kubernetes.Interface - appclientset appclientset.Interface - log *log.Entry + ns string + staticAssetsDir string + kubeclientset kubernetes.Interface + appclientset appclientset.Interface + log *log.Entry } // NewServer returns a new instance of the ArgoCD API server -func NewServer(kubeclientset kubernetes.Interface, appclientset appclientset.Interface) *ArgoCDServer { +func NewServer(kubeclientset kubernetes.Interface, appclientset appclientset.Interface, staticAssetsDir string) *ArgoCDServer { return &ArgoCDServer{ - ns: "default", - kubeclientset: kubeclientset, - appclientset: appclientset, - log: log.NewEntry(log.New()), + ns: "default", + kubeclientset: kubeclientset, + appclientset: appclientset, + log: log.NewEntry(log.New()), + staticAssetsDir: staticAssetsDir, } } @@ -94,12 +98,33 @@ func (a *ArgoCDServer) Run() { // we use our own Marshaler gwMuxOpts := runtime.WithMarshalerOption(runtime.MIMEWildcard, new(jsonutil.JSONMarshaler)) gwmux := runtime.NewServeMux(gwMuxOpts) - mux.Handle("/", gwmux) + mux.Handle("/api/", gwmux) dOpts := []grpc.DialOption{grpc.WithInsecure()} mustRegisterGWHandler(version.RegisterVersionServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts) mustRegisterGWHandler(cluster.RegisterClusterServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts) mustRegisterGWHandler(application.RegisterApplicationServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts) mustRegisterGWHandler(repository.RegisterRepositoryServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts) + + if a.staticAssetsDir != "" { + mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { + acceptHtml := false + for _, acceptType := range strings.Split(request.Header.Get("Accept"), ",") { + if acceptType == "text/html" || acceptType == "html" { + acceptHtml = true + break + } + } + fileRequest := request.URL.Path != "/index.html" && strings.Contains(request.URL.Path, ".") + + // serve index.html for non file requests to support HTML5 History API + if acceptHtml && !fileRequest && (request.Method == "GET" || request.Method == "HEAD") { + http.ServeFile(writer, request, a.staticAssetsDir+"/index.html") + } else { + http.ServeFile(writer, request, a.staticAssetsDir+request.URL.Path) + } + }) + } + httpS := &http.Server{ Addr: endpoint, Handler: mux, diff --git a/test/e2e/fixture.go b/test/e2e/fixture.go index 0938bac336507..75a1d4c2d0077 100644 --- a/test/e2e/fixture.go +++ b/test/e2e/fixture.go @@ -52,8 +52,8 @@ func createNamespace(kubeClient *kubernetes.Clientset) (string, error) { func (f *Fixture) setup() error { common.NewInstaller(f.ExtensionsClient, f.KubeClient).Install(common.InstallParameters{ - DryRun: false, - SkipController: true, + DryRun: false, + CrdOnly: true, }) return nil }