feat/release: appliance setup workflow and ui (#63291)

This adds the Appliance setup workflow UI. Completes
https://linear.app/sourcegraph/issue/REL-73/the-user-can-perform-initial-installation-from-a-web-ui.

- Adds basic HTTP routing for frontend UI using server side rendering
and HTMX for more interactivity when needed.
- SSR allows for an extremely simple frontend that uses almost no JS/TS
and simple HTTP.
- Uses `go/embed` to embed static assets and libraries in the binary
making for an extremely simple deployment.
- Adds some basic styling for UI
- Adds foundation for tracking installation progress of install

![Sourcegraph Appliance - Setup · 9 32am ·
06-17](https://github.com/sourcegraph/sourcegraph/assets/1760552/d3a0c1ba-56fc-4de7-9dd0-d7cef7419457)
This commit is contained in:
Jacob Pleiness 2024-06-19 11:21:13 -04:00 committed by GitHub
parent b3b7936ffa
commit fec72ef241
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 481 additions and 110 deletions

View File

@ -66,3 +66,7 @@ wolfi-images/*.lock.json
# This is downloaded from upstream directly and should not be modified
dev/linearhooks/internal/lineargql/schema.graphql
# This is an embedded external minified library and should not be modified
internal/appliance/web/static/script/htmx.min.js
internal/appliance/web/static/script/bootstrap.bundle.min.js

View File

@ -18,6 +18,7 @@ type Config struct {
k8sConfig *rest.Config
metrics metricsConfig
grpc grpcConfig
http httpConfig
namespace string
}
@ -36,9 +37,10 @@ func (c *Config) Load() {
}
c.k8sConfig = k8sConfig
c.metrics.addr = c.Get("APPLIANCE_METRICS_ADDR", ":8080", "Appliance metrics server address.")
c.metrics.addr = c.Get("APPLIANCE_METRICS_ADDR", ":8734", "Appliance metrics server address.")
c.metrics.secure = c.GetBool("APPLIANCE_METRICS_SECURE", "false", "Appliance metrics server uses https.")
c.grpc.addr = c.Get("APPLIANCE_GRPC_ADDR", ":9000", "Appliance gRPC address.")
c.http.addr = c.Get("APPLIANCE_HTTP_ADDR", ":8080", "Appliance http address.")
c.namespace = c.Get("APPLIANCE_NAMESPACE", cache.AllNamespaces, "Namespace to monitor. Defaults to all.")
}
@ -55,3 +57,7 @@ type metricsConfig struct {
type grpcConfig struct {
addr string
}
type httpConfig struct {
addr string
}

View File

@ -3,9 +3,11 @@ package shared
import (
"context"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
@ -23,6 +25,7 @@ import (
"github.com/sourcegraph/sourcegraph/internal/grpc/defaults"
"github.com/sourcegraph/sourcegraph/internal/observation"
"github.com/sourcegraph/sourcegraph/internal/service"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
var onlyOneSignalHandler = make(chan struct{})
@ -39,7 +42,7 @@ func Start(ctx context.Context, observationCtx *observation.Context, ready servi
return err
}
app := appliance.NewAppliance(k8sClient)
app := appliance.NewAppliance(k8sClient, logger)
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Logger: logr,
@ -67,15 +70,20 @@ func Start(ctx context.Context, observationCtx *observation.Context, ready servi
return err
}
// Mark health server as ready
ready()
listener, err := net.Listen("tcp", config.grpc.addr)
if err != nil {
logger.Error("unable to create tcp listener", log.Error(err))
return err
}
srv := &http.Server{
Addr: config.http.addr,
Handler: app.Routes(),
IdleTimeout: time.Minute,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
grpcServer := makeGRPCServer(logger, app)
g, ctx := errgroup.WithContext(ctx)
@ -89,7 +97,14 @@ func Start(ctx context.Context, observationCtx *observation.Context, ready servi
}
return nil
})
g.Go(func() error {
logger.Info("http server listening", log.String("address", config.http.addr))
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Error("problem running http server", log.Error(err))
return err
}
return nil
})
g.Go(func() error {
logger.Info("starting manager")
if err := mgr.Start(ctx); err != nil {
@ -98,14 +113,18 @@ func Start(ctx context.Context, observationCtx *observation.Context, ready servi
}
return nil
})
g.Go(func() error {
<-ctx.Done()
grpcServer.GracefulStop()
logger.Info("shutting down gRPC server gracefully")
_ = srv.Shutdown(ctx)
logger.Info("shutting down http server gracefully")
return ctx.Err()
})
// Mark health server as ready
ready()
return g.Wait()
}

View File

@ -4,7 +4,18 @@ go_library(
name = "appliance",
srcs = [
"appliance.go",
"embed.go",
"grpc.go",
"html.go",
"routes.go",
],
embedsrcs = [
"web/static/img/favicon.png",
"web/static/script/htmx.min.js",
"web/template/setup.gohtml",
"web/static/css/bootstrap.min.css",
"web/static/css/custom.css",
"web/static/script/bootstrap.bundle.min.js",
],
importpath = "github.com/sourcegraph/sourcegraph/internal/appliance",
visibility = ["//:__subpackages__"],
@ -12,9 +23,12 @@ go_library(
"//internal/appliance/config",
"//internal/appliance/v1:appliance",
"//lib/pointers",
"@com_github_masterminds_semver//:semver",
"@com_github_gorilla_mux//:mux",
"@com_github_sourcegraph_log//:log",
"@io_k8s_api//core/v1:core",
"@io_k8s_apimachinery//pkg/api/errors",
"@io_k8s_apimachinery//pkg/apis/meta/v1:meta",
"@io_k8s_apimachinery//pkg/types",
"@io_k8s_sigs_controller_runtime//pkg/client",
"@io_k8s_sigs_yaml//:yaml",
],

View File

@ -3,9 +3,11 @@ package appliance
import (
"context"
"github.com/Masterminds/semver"
"github.com/sourcegraph/log"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
@ -15,11 +17,10 @@ import (
)
type Appliance struct {
client client.Client
version semver.Version
client client.Client
status Status
sourcegraph config.Sourcegraph
logger log.Logger
// Embed the UnimplementedApplianceServiceServer structs to ensure forwards compatibility (if the service is
// compiled against a newer version of the proto file, the server will still have default implementations of any new
@ -27,43 +28,34 @@ type Appliance struct {
pb.UnimplementedApplianceServiceServer
}
type Status struct {
stage Stage
}
// Stage is a Stage that an Appliance can be in.
type Stage string
// Status is a Stage that an Appliance can be in.
type Status string
const (
StageUnknown Stage = "unknown"
StageIdle Stage = "idle"
StageInstall Stage = "install"
StageInstalling Stage = "installing"
StageUpgrading Stage = "upgrading"
StageWaitingForAdmin Stage = "waitingForAdmin"
StageRefresh Stage = "refresh"
StatusUnknown Status = "unknown"
StatusSetup Status = "setup"
StatusInstalling Status = "installing"
)
func (s Stage) String() string {
func (s Status) String() string {
return string(s)
}
func NewAppliance(client client.Client) *Appliance {
func NewAppliance(client client.Client, logger log.Logger) *Appliance {
return &Appliance{
client: client,
status: Status{
stage: StageUnknown,
},
client: client,
status: StatusSetup,
sourcegraph: config.Sourcegraph{},
logger: logger,
}
}
func (a *Appliance) GetCurrentVersion(ctx context.Context) semver.Version {
return a.version
func (a *Appliance) GetCurrentVersion(ctx context.Context) string {
return a.sourcegraph.Status.CurrentVersion
}
func (a *Appliance) GetCurrentStage(ctx context.Context) Stage {
return a.status.stage
func (a *Appliance) GetCurrentStatus(ctx context.Context) Status {
return a.status
}
func (a *Appliance) CreateConfigMap(ctx context.Context, name, namespace string) (*corev1.ConfigMap, error) {
@ -90,5 +82,40 @@ func (a *Appliance) CreateConfigMap(ctx context.Context, name, namespace string)
},
}
if err := a.client.Create(ctx, configMap); err != nil {
return nil, err
}
return configMap, nil
}
func (a *Appliance) GetConfigMap(ctx context.Context, name, namespace string) (*corev1.ConfigMap, error) {
var applianceSpec corev1.ConfigMap
err := a.client.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, &applianceSpec)
if apierrors.IsNotFound(err) {
return nil, nil
} else if err != nil {
return nil, err
}
return &applianceSpec, nil
}
func (a *Appliance) shouldSetupRun(ctx context.Context) (bool, error) {
cfgMap, err := a.GetConfigMap(ctx, "sourcegraph-appliance", "default") //TODO unhardcode and load namespace properly
switch {
case err != nil:
return false, err
case a.status == StatusInstalling:
// configMap does not exist but is being created
return false, nil
case cfgMap == nil:
// configMap does not exist
return true, nil
case cfgMap.Annotations[config.AnnotationKeyManaged] == "false":
// appliance is not managed
return false, nil
default:
return true, nil
}
}

View File

@ -266,11 +266,19 @@ type SourcegraphSpec struct {
StorageClass StorageClassSpec `json:"storageClass,omitempty"`
}
// SetupStatus defines the observes status of the setup process.
type SetupStatus struct {
Progress int32
}
// SourcegraphStatus defines the observed state of Sourcegraph
type SourcegraphStatus struct {
// CurrentVersion is the version of Sourcegraph currently running.
CurrentVersion string `json:"currentVersion"`
// Setup tracks the progress of the setup process.
Setup SetupStatus `json:"setup,omitempty"`
// Represents the latest available observations of Sourcegraph's current state.
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

View File

@ -0,0 +1,15 @@
package appliance
import (
"embed"
"io/fs"
)
var (
//go:embed web/static
staticFiles embed.FS
staticFS, _ = fs.Sub(staticFiles, "web/static")
//go:embed web/template
templateFS embed.FS
)

View File

@ -7,13 +7,11 @@ import (
)
func (a *Appliance) GetApplianceVersion(ctx context.Context, request *pb.GetApplianceVersionRequest) (*pb.GetApplianceVersionResponse, error) {
version := a.GetCurrentVersion(ctx)
return &pb.GetApplianceVersionResponse{Version: version.String()}, nil
return &pb.GetApplianceVersionResponse{Version: a.GetCurrentVersion(ctx)}, nil
}
func (a *Appliance) GetApplianceStage(ctx context.Context, request *pb.GetApplianceStageRequest) (*pb.GetApplianceStageResponse, error) {
stage := a.GetCurrentStage(ctx)
func (a *Appliance) GetApplianceStatus(ctx context.Context, request *pb.GetApplianceStatusRequest) (*pb.GetApplianceStatusResponse, error) {
status := a.GetCurrentStatus(ctx)
return &pb.GetApplianceStageResponse{Stage: stage.String()}, nil
return &pb.GetApplianceStatusResponse{Status: status.String()}, nil
}

View File

@ -0,0 +1,80 @@
package appliance
import (
"context"
"html/template"
"net/http"
"github.com/sourcegraph/log"
"github.com/sourcegraph/sourcegraph/internal/appliance/config"
)
const (
templateSetup = "web/template/setup.gohtml"
)
var (
setupTmpl *template.Template
)
func init() {
setupTmpl = template.Must(template.ParseFS(templateFS, templateSetup))
}
func (a *Appliance) applianceHandler(w http.ResponseWriter, r *http.Request) {
if ok, _ := a.shouldSetupRun(context.Background()); ok {
http.Redirect(w, r, "/appliance/setup", http.StatusSeeOther)
}
}
func (a *Appliance) getSetupHandler(w http.ResponseWriter, r *http.Request) {
err := setupTmpl.Execute(w, "")
if err != nil {
a.logger.Error("failed to execute templating", log.Error(err))
// Handle err
}
}
func (a *Appliance) postSetupHandler(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
a.logger.Error("failed to parse http form request", log.Error(err))
// Handle err
}
a.sourcegraph.Spec.RequestedVersion = r.FormValue("version")
if r.FormValue("external_database") == "yes" {
a.sourcegraph.Spec.PGSQL.DatabaseConnection = &config.DatabaseConnectionSpec{
Host: r.FormValue("pgsqlDBHost"),
Port: r.FormValue("pgsqlDBPort"),
User: r.FormValue("pgsqlDBUser"),
Password: r.FormValue("pgsqlDBPassword"),
Database: r.FormValue("pgsqlDBName"),
}
a.sourcegraph.Spec.CodeIntel.DatabaseConnection = &config.DatabaseConnectionSpec{
Host: r.FormValue("codeintelDBHost"),
Port: r.FormValue("codeintelDBPort"),
User: r.FormValue("codeintelDBUser"),
Password: r.FormValue("codeintelDBPassword"),
Database: r.FormValue("codeintelDBName"),
}
a.sourcegraph.Spec.CodeInsights.DatabaseConnection = &config.DatabaseConnectionSpec{
Host: r.FormValue("codeinsightsDBHost"),
Port: r.FormValue("codeinsightsDBPort"),
User: r.FormValue("codeinsightsDBUser"),
Password: r.FormValue("codeinsightsDBPassword"),
Database: r.FormValue("codeinsightsDBName"),
}
}
// TODO validate user input
_, err = a.CreateConfigMap(r.Context(), "sourcegraph-appliance", "default") //TODO namespace
if err != nil {
a.logger.Error("failed to create configMap sourcegraph-appliance", log.Error(err))
// Handle err
}
a.status = StatusInstalling
http.Redirect(w, r, "/appliance", http.StatusSeeOther)
}

View File

@ -0,0 +1,20 @@
package appliance
import (
"net/http"
"github.com/gorilla/mux"
)
func (a *Appliance) Routes() *mux.Router {
r := mux.NewRouter()
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/appliance", http.StatusFound)
})
r.HandleFunc("/appliance", a.applianceHandler).Methods(http.MethodGet)
r.HandleFunc("/appliance/setup", a.getSetupHandler).Methods(http.MethodGet)
r.HandleFunc("/appliance/setup", a.postSetupHandler).Methods(http.MethodPost)
return r
}

View File

@ -105,14 +105,14 @@ func (x *GetApplianceVersionResponse) GetVersion() string {
return ""
}
type GetApplianceStageRequest struct {
type GetApplianceStatusRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetApplianceStageRequest) Reset() {
*x = GetApplianceStageRequest{}
func (x *GetApplianceStatusRequest) Reset() {
*x = GetApplianceStatusRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_appliance_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@ -120,13 +120,13 @@ func (x *GetApplianceStageRequest) Reset() {
}
}
func (x *GetApplianceStageRequest) String() string {
func (x *GetApplianceStatusRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetApplianceStageRequest) ProtoMessage() {}
func (*GetApplianceStatusRequest) ProtoMessage() {}
func (x *GetApplianceStageRequest) ProtoReflect() protoreflect.Message {
func (x *GetApplianceStatusRequest) ProtoReflect() protoreflect.Message {
mi := &file_appliance_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@ -138,21 +138,21 @@ func (x *GetApplianceStageRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use GetApplianceStageRequest.ProtoReflect.Descriptor instead.
func (*GetApplianceStageRequest) Descriptor() ([]byte, []int) {
// Deprecated: Use GetApplianceStatusRequest.ProtoReflect.Descriptor instead.
func (*GetApplianceStatusRequest) Descriptor() ([]byte, []int) {
return file_appliance_proto_rawDescGZIP(), []int{2}
}
type GetApplianceStageResponse struct {
type GetApplianceStatusResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Stage string `protobuf:"bytes,1,opt,name=stage,proto3" json:"stage,omitempty"`
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *GetApplianceStageResponse) Reset() {
*x = GetApplianceStageResponse{}
func (x *GetApplianceStatusResponse) Reset() {
*x = GetApplianceStatusResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_appliance_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@ -160,13 +160,13 @@ func (x *GetApplianceStageResponse) Reset() {
}
}
func (x *GetApplianceStageResponse) String() string {
func (x *GetApplianceStatusResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetApplianceStageResponse) ProtoMessage() {}
func (*GetApplianceStatusResponse) ProtoMessage() {}
func (x *GetApplianceStageResponse) ProtoReflect() protoreflect.Message {
func (x *GetApplianceStatusResponse) ProtoReflect() protoreflect.Message {
mi := &file_appliance_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@ -178,14 +178,14 @@ func (x *GetApplianceStageResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use GetApplianceStageResponse.ProtoReflect.Descriptor instead.
func (*GetApplianceStageResponse) Descriptor() ([]byte, []int) {
// Deprecated: Use GetApplianceStatusResponse.ProtoReflect.Descriptor instead.
func (*GetApplianceStatusResponse) Descriptor() ([]byte, []int) {
return file_appliance_proto_rawDescGZIP(), []int{3}
}
func (x *GetApplianceStageResponse) GetStage() string {
func (x *GetApplianceStatusResponse) GetStatus() string {
if x != nil {
return x.Stage
return x.Status
}
return ""
}
@ -200,31 +200,32 @@ var file_appliance_proto_rawDesc = []byte{
0x1b, 0x47, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x56, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x1a, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x41, 0x70, 0x70,
0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x22, 0x31, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e,
0x63, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x73, 0x74, 0x61, 0x67, 0x65, 0x32, 0xee, 0x01, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61,
0x6e, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6f, 0x0a, 0x13, 0x47, 0x65,
0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x12, 0x28, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31,
0x2e, 0x47, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x56, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x61, 0x70,
0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x70,
0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x69, 0x0a, 0x11, 0x47,
0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65,
0x12, 0x26, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x47, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x67,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69,
0x61, 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69,
0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68,
0x2f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2f, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x2f,
0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x1b, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x41, 0x70, 0x70,
0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x22, 0x34, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61,
0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x32, 0xf1, 0x01, 0x0a, 0x10, 0x41, 0x70,
0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6f,
0x0a, 0x13, 0x47, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x56, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63,
0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x29, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47,
0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12,
0x6c, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x27, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63,
0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28,
0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65,
0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x42, 0x3a, 0x5a,
0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, 0x72,
0x61, 0x70, 0x68, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x70,
0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
@ -243,14 +244,14 @@ var file_appliance_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_appliance_proto_goTypes = []interface{}{
(*GetApplianceVersionRequest)(nil), // 0: appliance.v1.GetApplianceVersionRequest
(*GetApplianceVersionResponse)(nil), // 1: appliance.v1.GetApplianceVersionResponse
(*GetApplianceStageRequest)(nil), // 2: appliance.v1.GetApplianceStageRequest
(*GetApplianceStageResponse)(nil), // 3: appliance.v1.GetApplianceStageResponse
(*GetApplianceStatusRequest)(nil), // 2: appliance.v1.GetApplianceStatusRequest
(*GetApplianceStatusResponse)(nil), // 3: appliance.v1.GetApplianceStatusResponse
}
var file_appliance_proto_depIdxs = []int32{
0, // 0: appliance.v1.ApplianceService.GetApplianceVersion:input_type -> appliance.v1.GetApplianceVersionRequest
2, // 1: appliance.v1.ApplianceService.GetApplianceStage:input_type -> appliance.v1.GetApplianceStageRequest
2, // 1: appliance.v1.ApplianceService.GetApplianceStatus:input_type -> appliance.v1.GetApplianceStatusRequest
1, // 2: appliance.v1.ApplianceService.GetApplianceVersion:output_type -> appliance.v1.GetApplianceVersionResponse
3, // 3: appliance.v1.ApplianceService.GetApplianceStage:output_type -> appliance.v1.GetApplianceStageResponse
3, // 3: appliance.v1.ApplianceService.GetApplianceStatus:output_type -> appliance.v1.GetApplianceStatusResponse
2, // [2:4] is the sub-list for method output_type
0, // [0:2] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
@ -289,7 +290,7 @@ func file_appliance_proto_init() {
}
}
file_appliance_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetApplianceStageRequest); i {
switch v := v.(*GetApplianceStatusRequest); i {
case 0:
return &v.state
case 1:
@ -301,7 +302,7 @@ func file_appliance_proto_init() {
}
}
file_appliance_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetApplianceStageResponse); i {
switch v := v.(*GetApplianceStatusResponse); i {
case 0:
return &v.state
case 1:

View File

@ -9,7 +9,7 @@ service ApplianceService {
option idempotency_level = NO_SIDE_EFFECTS;
}
rpc GetApplianceStage(GetApplianceStageRequest) returns (GetApplianceStageResponse) {
rpc GetApplianceStatus(GetApplianceStatusRequest) returns (GetApplianceStatusResponse) {
option idempotency_level = NO_SIDE_EFFECTS;
}
}
@ -20,8 +20,8 @@ message GetApplianceVersionResponse {
string version = 1;
}
message GetApplianceStageRequest {}
message GetApplianceStatusRequest {}
message GetApplianceStageResponse {
string stage = 1;
message GetApplianceStatusResponse {
string status = 1;
}

View File

@ -20,7 +20,7 @@ const _ = grpc.SupportPackageIsVersion7
const (
ApplianceService_GetApplianceVersion_FullMethodName = "/appliance.v1.ApplianceService/GetApplianceVersion"
ApplianceService_GetApplianceStage_FullMethodName = "/appliance.v1.ApplianceService/GetApplianceStage"
ApplianceService_GetApplianceStatus_FullMethodName = "/appliance.v1.ApplianceService/GetApplianceStatus"
)
// ApplianceServiceClient is the client API for ApplianceService service.
@ -28,7 +28,7 @@ const (
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ApplianceServiceClient interface {
GetApplianceVersion(ctx context.Context, in *GetApplianceVersionRequest, opts ...grpc.CallOption) (*GetApplianceVersionResponse, error)
GetApplianceStage(ctx context.Context, in *GetApplianceStageRequest, opts ...grpc.CallOption) (*GetApplianceStageResponse, error)
GetApplianceStatus(ctx context.Context, in *GetApplianceStatusRequest, opts ...grpc.CallOption) (*GetApplianceStatusResponse, error)
}
type applianceServiceClient struct {
@ -48,9 +48,9 @@ func (c *applianceServiceClient) GetApplianceVersion(ctx context.Context, in *Ge
return out, nil
}
func (c *applianceServiceClient) GetApplianceStage(ctx context.Context, in *GetApplianceStageRequest, opts ...grpc.CallOption) (*GetApplianceStageResponse, error) {
out := new(GetApplianceStageResponse)
err := c.cc.Invoke(ctx, ApplianceService_GetApplianceStage_FullMethodName, in, out, opts...)
func (c *applianceServiceClient) GetApplianceStatus(ctx context.Context, in *GetApplianceStatusRequest, opts ...grpc.CallOption) (*GetApplianceStatusResponse, error) {
out := new(GetApplianceStatusResponse)
err := c.cc.Invoke(ctx, ApplianceService_GetApplianceStatus_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -62,7 +62,7 @@ func (c *applianceServiceClient) GetApplianceStage(ctx context.Context, in *GetA
// for forward compatibility
type ApplianceServiceServer interface {
GetApplianceVersion(context.Context, *GetApplianceVersionRequest) (*GetApplianceVersionResponse, error)
GetApplianceStage(context.Context, *GetApplianceStageRequest) (*GetApplianceStageResponse, error)
GetApplianceStatus(context.Context, *GetApplianceStatusRequest) (*GetApplianceStatusResponse, error)
mustEmbedUnimplementedApplianceServiceServer()
}
@ -73,8 +73,8 @@ type UnimplementedApplianceServiceServer struct {
func (UnimplementedApplianceServiceServer) GetApplianceVersion(context.Context, *GetApplianceVersionRequest) (*GetApplianceVersionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetApplianceVersion not implemented")
}
func (UnimplementedApplianceServiceServer) GetApplianceStage(context.Context, *GetApplianceStageRequest) (*GetApplianceStageResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetApplianceStage not implemented")
func (UnimplementedApplianceServiceServer) GetApplianceStatus(context.Context, *GetApplianceStatusRequest) (*GetApplianceStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetApplianceStatus not implemented")
}
func (UnimplementedApplianceServiceServer) mustEmbedUnimplementedApplianceServiceServer() {}
@ -107,20 +107,20 @@ func _ApplianceService_GetApplianceVersion_Handler(srv interface{}, ctx context.
return interceptor(ctx, in, info, handler)
}
func _ApplianceService_GetApplianceStage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetApplianceStageRequest)
func _ApplianceService_GetApplianceStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetApplianceStatusRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ApplianceServiceServer).GetApplianceStage(ctx, in)
return srv.(ApplianceServiceServer).GetApplianceStatus(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ApplianceService_GetApplianceStage_FullMethodName,
FullMethod: ApplianceService_GetApplianceStatus_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ApplianceServiceServer).GetApplianceStage(ctx, req.(*GetApplianceStageRequest))
return srv.(ApplianceServiceServer).GetApplianceStatus(ctx, req.(*GetApplianceStatusRequest))
}
return interceptor(ctx, in, info, handler)
}
@ -137,8 +137,8 @@ var ApplianceService_ServiceDesc = grpc.ServiceDesc{
Handler: _ApplianceService_GetApplianceVersion_Handler,
},
{
MethodName: "GetApplianceStage",
Handler: _ApplianceService_GetApplianceStage_Handler,
MethodName: "GetApplianceStatus",
Handler: _ApplianceService_GetApplianceStatus_Handler,
},
},
Streams: []grpc.StreamDesc{},

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,17 @@
/* Define custom CSS variables */
:root {
--bs-primary: #3498db;
--bs-secondary: #2ecc71;
--bs-success: #28a745;
--bs-info: #17a2b8;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #343a40;
}
/* Override Bootstrap variables */
body {
background-color: var(--bs-light);
color: var(--bs-dark);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,148 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.png" />
<link rel="stylesheet" type="text/css" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="/static/css/custom.css">
<script src="/static/script/htmx.min.js"></script>
<script src="/static/script/bootstrap.bundle.min.js"></script>
<title>Sourcegraph Appliance - Setup</title>
</head>
<body hx-boost="true" class="container">
<h1>Sourcegraph Appliance Setup</h1>
<form action="/appliance/setup" method="post">
<div class="row">
<div class="col">
<label for="version" class="fw-bold">Sourcegraph Version</label>
<p class="fw-light">Choose the Sourcegraph version that you would like to install.</p>
</div>
<div class="col">
<select id="version" name="version" class="row form-select mb-3" aria-label="Sourcegraph version selection">
<option selected>Latest</option>
<option value="5.4">5.4</option>
<option value="5.3">5.3</option>
</select>
</div>
</div>
<hr/>
<div class="row">
<div class="col">
<label for="external_database" class="fw-bold">Database</label>
<p>Would you like to use an external database?</p>
</div>
<div class="col">
<div class="form-switch mb-3">
<input class="form-check-input" data-bs-toggle="collapse" href="#database" type="checkbox" id="external_database" name="external_database">
<label class="form-check-label" for="external_database">Use an external database</label>
</div>
<div class="accordion collapse mb-3" id="database">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
<strong>Sourcegraph Database</strong>
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#database">
<div class="accordion-body">
<div class="mb-3">
<label for="pgsqlDBHost" class="form-label">Database Host</label>
<input type="text" class="form-control" id="pgsqlDBHost" name="pgsqlDBHost" placeholder="hostname">
</div>
<div class="mb-3">
<label for="pgsqlDBPort" class="form-label">Database Port</label>
<input type="text" class="form-control" id="pgsqlDBPort" name="pgsqlDBPort" placeholder="5432">
</div>
<div class="mb-3">
<label for="pgsqlDBUser" class="form-label">Database User</label>
<input type="text" class="form-control" id="pgsqlDBUser" name="pgsqlDBUser" placeholder="username">
</div>
<div class="mb-3">
<label for="pgsqlDBPassword" class="form-label">Database Password</label>
<input type="password" class="form-control" id="pgsqlDBPassword" name="pgsqlDBPassword" placeholder="password">
</div>
<div class="mb-3">
<label for="pgsqlDBName" class="form-label">Database Name</label>
<input type="text" class="form-control" id="pgsqlDBName" name="pgsqlDBName" placeholder="sg">
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
<strong>CodeIntel Database</strong>
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#database">
<div class="accordion-body">
<div class="mb-3">
<label for="codeintelDBHost" class="form-label">Database Host</label>
<input type="text" class="form-control" id="codeintelDBHost" name="codeintelDBHost" placeholder="hostname">
</div>
<div class="mb-3">
<label for="codeintelDBPort" class="form-label">Database Port</label>
<input type="text" class="form-control" id="codeintelDBPort" name="codeintelDBPort" placeholder="5432">
</div>
<div class="mb-3">
<label for="codeintelDBUser" class="form-label">Database User</label>
<input type="text" class="form-control" id="codeintelDBUser" name="codeintelDBUser" placeholder="username">
</div>
<div class="mb-3">
<label for="codeintelDBPassword" class="form-label">Database Password</label>
<input type="password" class="form-control" id="codeintelDBPassword" name="codeintelDBPassword" placeholder="password">
</div>
<div class="mb-3">
<label for="codeintelDBName" class="form-label">Database Name</label>
<input type="text" class="form-control" id="codeintelDBName" name="codeintelDBName" placeholder="sg">
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
<strong>CodeInsights Database</strong>
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" data-bs-parent="#database">
<div class="accordion-body">
<div class="mb-3">
<label for="codeinsightsDBHost" class="form-label">Database Host</label>
<input type="text" class="form-control" id="codeinsightsDBHost" name="codeinsightsDBHost" placeholder="hostname">
</div>
<div class="mb-3">
<label for="codeinsightsDBPort" class="form-label">Database Port</label>
<input type="text" class="form-control" id="codeinsightsDBPort" name="codeinsightsDBPort" placeholder="5432">
</div>
<div class="mb-3">
<label for="codeinsightsDBUser" class="form-label">Database User</label>
<input type="text" class="form-control" id="codeinsightsDBUser" name="codeinsightsDBUser" placeholder="username">
</div>
<div class="mb-3">
<label for="codeinsightsDBPassword" class="form-label">Database Password</label>
<input type="password" class="form-control" id="codeinsightsDBPassword" name="codeinsightsDBPassword" placeholder="password">
</div>
<div class="mb-3">
<label for="codeinsightsDBName" class="form-label">Database Name</label>
<input type="text" class="form-control" id="codeinsightsDBName" name="codeinsightsDBName" placeholder="sg">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Start Setup</button>
</form>
</body>
</html>