diff --git a/cmd/relui/main.go b/cmd/relui/main.go index e20c6fb80c..9c1587f09b 100644 --- a/cmd/relui/main.go +++ b/cmd/relui/main.go @@ -5,13 +5,18 @@ package main import ( + "io/ioutil" "log" "net/http" "os" + "path/filepath" + + "github.com/golang/protobuf/proto" + reluipb "golang.org/x/build/cmd/relui/protos" ) func main() { - s := &server{store: &memoryStore{}} + s := &server{store: &memoryStore{}, configs: loadWorkflowConfig("./workflows")} http.Handle("/workflows/create", http.HandlerFunc(s.createWorkflowHandler)) http.Handle("/workflows/new", http.HandlerFunc(s.newWorkflowHandler)) http.Handle("/", fileServerHandler(relativeFile("./static"), http.HandlerFunc(s.homeHandler))) @@ -23,3 +28,28 @@ func main() { log.Printf("Listening on :" + port) log.Fatal(http.ListenAndServe(":"+port, http.DefaultServeMux)) } + +// loadWorkflowConfig loads Workflow configuration files from dir. It expects all files to be in textproto format. +func loadWorkflowConfig(dir string) []*reluipb.Workflow { + fs, err := filepath.Glob(filepath.Join(relativeFile(dir), "*.textpb")) + if err != nil { + log.Fatalf("Error perusing %q for configuration", filepath.Join(dir, "*.textpb")) + } + if len(fs) == 0 { + log.Println("No workflow configuration found.") + } + var ws []*reluipb.Workflow + for _, f := range fs { + b, err := ioutil.ReadFile(f) + if err != nil { + log.Printf("ioutil.ReadFile(%q) = _, %v, wanted no error", f, err) + } + w := new(reluipb.Workflow) + if err = proto.UnmarshalText(string(b), w); err != nil { + log.Printf("Error unmarshalling Workflow from %q: %v", f, err) + continue + } + ws = append(ws, w) + } + return ws +} diff --git a/cmd/relui/protos/protos.go b/cmd/relui/protos/protos.go new file mode 100644 index 0000000000..9e4bded787 --- /dev/null +++ b/cmd/relui/protos/protos.go @@ -0,0 +1,11 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package protos + +// Run "go generate" in this directory to update. You need to have: +// +// - a protoc binary (see https://github.com/golang/protobuf#installation) +// - go get -u github.com/golang/protobuf/protoc-gen-go + +//go:generate protoc --proto_path=$GOPATH/src:. --go_out=plugins=grpc:. relui.proto diff --git a/cmd/relui/protos/relui.pb.go b/cmd/relui/protos/relui.pb.go new file mode 100644 index 0000000000..039aaa6a44 --- /dev/null +++ b/cmd/relui/protos/relui.pb.go @@ -0,0 +1,281 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: relui.proto + +package protos + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type TaskStatus int32 + +const ( + TaskStatus_TASK_STATUS_UNKNOWN TaskStatus = 0 + TaskStatus_TASK_STATUS_CREATED TaskStatus = 1 + TaskStatus_TASK_STATUS_STARTED TaskStatus = 2 +) + +var TaskStatus_name = map[int32]string{ + 0: "TASK_STATUS_UNKNOWN", + 1: "TASK_STATUS_CREATED", + 2: "TASK_STATUS_STARTED", +} + +var TaskStatus_value = map[string]int32{ + "TASK_STATUS_UNKNOWN": 0, + "TASK_STATUS_CREATED": 1, + "TASK_STATUS_STARTED": 2, +} + +func (x TaskStatus) String() string { + return proto.EnumName(TaskStatus_name, int32(x)) +} + +func (TaskStatus) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_6de8859f82adce0a, []int{0} +} + +type Workflow struct { + // name is a unique name for a workflow, such as local_go_release. The name must be unique across + // all workflow configurations. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // buildable_asks is a list of tasks to be performed by the workflow. + BuildableTasks []*BuildableTask `protobuf:"bytes,2,rep,name=buildable_tasks,json=buildableTasks,proto3" json:"buildable_tasks,omitempty"` + // params are parameters provided when creating a workflow. + Params map[string]string `protobuf:"bytes,3,rep,name=params,proto3" json:"params,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Workflow) Reset() { *m = Workflow{} } +func (m *Workflow) String() string { return proto.CompactTextString(m) } +func (*Workflow) ProtoMessage() {} +func (*Workflow) Descriptor() ([]byte, []int) { + return fileDescriptor_6de8859f82adce0a, []int{0} +} + +func (m *Workflow) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Workflow.Unmarshal(m, b) +} +func (m *Workflow) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Workflow.Marshal(b, m, deterministic) +} +func (m *Workflow) XXX_Merge(src proto.Message) { + xxx_messageInfo_Workflow.Merge(m, src) +} +func (m *Workflow) XXX_Size() int { + return xxx_messageInfo_Workflow.Size(m) +} +func (m *Workflow) XXX_DiscardUnknown() { + xxx_messageInfo_Workflow.DiscardUnknown(m) +} + +var xxx_messageInfo_Workflow proto.InternalMessageInfo + +func (m *Workflow) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Workflow) GetBuildableTasks() []*BuildableTask { + if m != nil { + return m.BuildableTasks + } + return nil +} + +func (m *Workflow) GetParams() map[string]string { + if m != nil { + return m.Params + } + return nil +} + +type BuildableTask struct { + // name is a unique name for a task, such as fetch_go_source. The name must be unique across + // all workflow configurations. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // depends_on is the name of a task this task depends on. Artifacts from the depends_on task will be available + // to this task. + DependsOn string `protobuf:"bytes,2,opt,name=depends_on,json=dependsOn,proto3" json:"depends_on,omitempty"` + // task_status is the current status of a task. + Status TaskStatus `protobuf:"varint,3,opt,name=status,proto3,enum=protos.TaskStatus" json:"status,omitempty"` + // artifact_url is an optional URL to an artifact published by this task. + ArtifactUrl string `protobuf:"bytes,4,opt,name=artifact_url,json=artifactUrl,proto3" json:"artifact_url,omitempty"` + // git_source is an optional configuration for which git source to fetch. + GitSource *GitSource `protobuf:"bytes,5,opt,name=git_source,json=gitSource,proto3" json:"git_source,omitempty"` + // task_type is a unique type for a task, such as FetchGerritSource. Types are used by task runners to identify + // how to execute a task. + TaskType string `protobuf:"bytes,6,opt,name=task_type,json=taskType,proto3" json:"task_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *BuildableTask) Reset() { *m = BuildableTask{} } +func (m *BuildableTask) String() string { return proto.CompactTextString(m) } +func (*BuildableTask) ProtoMessage() {} +func (*BuildableTask) Descriptor() ([]byte, []int) { + return fileDescriptor_6de8859f82adce0a, []int{1} +} + +func (m *BuildableTask) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_BuildableTask.Unmarshal(m, b) +} +func (m *BuildableTask) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_BuildableTask.Marshal(b, m, deterministic) +} +func (m *BuildableTask) XXX_Merge(src proto.Message) { + xxx_messageInfo_BuildableTask.Merge(m, src) +} +func (m *BuildableTask) XXX_Size() int { + return xxx_messageInfo_BuildableTask.Size(m) +} +func (m *BuildableTask) XXX_DiscardUnknown() { + xxx_messageInfo_BuildableTask.DiscardUnknown(m) +} + +var xxx_messageInfo_BuildableTask proto.InternalMessageInfo + +func (m *BuildableTask) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *BuildableTask) GetDependsOn() string { + if m != nil { + return m.DependsOn + } + return "" +} + +func (m *BuildableTask) GetStatus() TaskStatus { + if m != nil { + return m.Status + } + return TaskStatus_TASK_STATUS_UNKNOWN +} + +func (m *BuildableTask) GetArtifactUrl() string { + if m != nil { + return m.ArtifactUrl + } + return "" +} + +func (m *BuildableTask) GetGitSource() *GitSource { + if m != nil { + return m.GitSource + } + return nil +} + +func (m *BuildableTask) GetTaskType() string { + if m != nil { + return m.TaskType + } + return "" +} + +type GitSource struct { + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Ref string `protobuf:"bytes,2,opt,name=ref,proto3" json:"ref,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GitSource) Reset() { *m = GitSource{} } +func (m *GitSource) String() string { return proto.CompactTextString(m) } +func (*GitSource) ProtoMessage() {} +func (*GitSource) Descriptor() ([]byte, []int) { + return fileDescriptor_6de8859f82adce0a, []int{2} +} + +func (m *GitSource) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GitSource.Unmarshal(m, b) +} +func (m *GitSource) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GitSource.Marshal(b, m, deterministic) +} +func (m *GitSource) XXX_Merge(src proto.Message) { + xxx_messageInfo_GitSource.Merge(m, src) +} +func (m *GitSource) XXX_Size() int { + return xxx_messageInfo_GitSource.Size(m) +} +func (m *GitSource) XXX_DiscardUnknown() { + xxx_messageInfo_GitSource.DiscardUnknown(m) +} + +var xxx_messageInfo_GitSource proto.InternalMessageInfo + +func (m *GitSource) GetUrl() string { + if m != nil { + return m.Url + } + return "" +} + +func (m *GitSource) GetRef() string { + if m != nil { + return m.Ref + } + return "" +} + +func init() { + proto.RegisterEnum("protos.TaskStatus", TaskStatus_name, TaskStatus_value) + proto.RegisterType((*Workflow)(nil), "protos.Workflow") + proto.RegisterMapType((map[string]string)(nil), "protos.Workflow.ParamsEntry") + proto.RegisterType((*BuildableTask)(nil), "protos.BuildableTask") + proto.RegisterType((*GitSource)(nil), "protos.GitSource") +} + +func init() { proto.RegisterFile("relui.proto", fileDescriptor_6de8859f82adce0a) } + +var fileDescriptor_6de8859f82adce0a = []byte{ + // 384 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0x4d, 0x6f, 0xda, 0x40, + 0x10, 0xed, 0x1a, 0xb0, 0xf0, 0xb8, 0xa5, 0x74, 0xdb, 0xaa, 0x56, 0x3f, 0x24, 0xca, 0xc9, 0xe2, + 0x40, 0x2b, 0xda, 0x43, 0x9b, 0x43, 0x24, 0x27, 0x41, 0x39, 0x20, 0x41, 0x64, 0x1b, 0x71, 0xb4, + 0xd6, 0xb0, 0x20, 0xcb, 0x8b, 0x6d, 0xed, 0xae, 0x13, 0xf9, 0x57, 0xe6, 0x2f, 0xe4, 0xa7, 0x44, + 0xbb, 0xd8, 0x09, 0x41, 0x39, 0xf9, 0xed, 0x7b, 0x33, 0x6f, 0xde, 0x8c, 0x0c, 0x36, 0xa7, 0xac, + 0x4c, 0xc6, 0x05, 0xcf, 0x65, 0x8e, 0x4d, 0xfd, 0x11, 0xc3, 0x7b, 0x04, 0xdd, 0x55, 0xce, 0xd3, + 0x2d, 0xcb, 0xef, 0x30, 0x86, 0x76, 0x46, 0xf6, 0xd4, 0x41, 0x03, 0xe4, 0x5a, 0xbe, 0xc6, 0xf8, + 0x1c, 0xde, 0xc7, 0x65, 0xc2, 0x36, 0x24, 0x66, 0x34, 0x92, 0x44, 0xa4, 0xc2, 0x31, 0x06, 0x2d, + 0xd7, 0x9e, 0x7c, 0x3e, 0x38, 0x89, 0xf1, 0x45, 0x23, 0x87, 0x44, 0xa4, 0x7e, 0x2f, 0x3e, 0x7e, + 0x0a, 0xfc, 0x17, 0xcc, 0x82, 0x70, 0xb2, 0x17, 0x4e, 0x4b, 0xb7, 0x7d, 0x6f, 0xda, 0x9a, 0xa9, + 0xe3, 0x1b, 0x2d, 0x4f, 0x33, 0xc9, 0x2b, 0xbf, 0xae, 0xfd, 0xfa, 0x1f, 0xec, 0x23, 0x1a, 0xf7, + 0xa1, 0x95, 0xd2, 0xaa, 0xce, 0xa5, 0x20, 0xfe, 0x04, 0x9d, 0x5b, 0xc2, 0x4a, 0xea, 0x18, 0x9a, + 0x3b, 0x3c, 0xce, 0x8c, 0x7f, 0x68, 0xf8, 0x80, 0xe0, 0xdd, 0x8b, 0x48, 0xaf, 0xae, 0xf5, 0x03, + 0x60, 0x43, 0x0b, 0x9a, 0x6d, 0x44, 0x94, 0x67, 0xb5, 0x89, 0x55, 0x33, 0x8b, 0x0c, 0x8f, 0xc0, + 0x14, 0x92, 0xc8, 0x52, 0xa5, 0x46, 0x6e, 0x6f, 0x82, 0x9b, 0xd4, 0xca, 0x30, 0xd0, 0x8a, 0x5f, + 0x57, 0xe0, 0x9f, 0xf0, 0x96, 0x70, 0x99, 0x6c, 0xc9, 0x5a, 0x46, 0x25, 0x67, 0x4e, 0x5b, 0x9b, + 0xd9, 0x0d, 0xb7, 0xe4, 0x0c, 0xff, 0x06, 0xd8, 0x25, 0x32, 0x12, 0x79, 0xc9, 0xd7, 0xd4, 0xe9, + 0x0c, 0x90, 0x6b, 0x4f, 0x3e, 0x34, 0x96, 0xd7, 0x89, 0x0c, 0xb4, 0xe0, 0x5b, 0xbb, 0x06, 0xe2, + 0x6f, 0x60, 0xa9, 0x63, 0x47, 0xb2, 0x2a, 0xa8, 0x63, 0x6a, 0xc7, 0xae, 0x22, 0xc2, 0xaa, 0xa0, + 0xc3, 0x5f, 0x60, 0x3d, 0x35, 0xa9, 0xdb, 0xa8, 0xa9, 0xf5, 0x6d, 0x4a, 0xce, 0x14, 0xc3, 0xe9, + 0xb6, 0x5e, 0x4a, 0xc1, 0xd1, 0x0a, 0xe0, 0x39, 0x38, 0xfe, 0x02, 0x1f, 0x43, 0x2f, 0x98, 0x45, + 0x41, 0xe8, 0x85, 0xcb, 0x20, 0x5a, 0xce, 0x67, 0xf3, 0xc5, 0x6a, 0xde, 0x7f, 0x73, 0x2a, 0x5c, + 0xfa, 0x53, 0x2f, 0x9c, 0x5e, 0xf5, 0xd1, 0xa9, 0x10, 0x84, 0x9e, 0xaf, 0x04, 0x23, 0x3e, 0xfc, + 0x46, 0x7f, 0x1e, 0x03, 0x00, 0x00, 0xff, 0xff, 0x73, 0x67, 0x81, 0x13, 0x5c, 0x02, 0x00, 0x00, +} diff --git a/cmd/relui/protos/relui.proto b/cmd/relui/protos/relui.proto new file mode 100644 index 0000000000..fa1be06aed --- /dev/null +++ b/cmd/relui/protos/relui.proto @@ -0,0 +1,53 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +syntax = "proto3"; + +package protos; + +message Workflow { + // name is a unique name for a workflow, such as local_go_release. The name must be unique across + // all workflow configurations. + string name = 1; + + // buildable_asks is a list of tasks to be performed by the workflow. + repeated BuildableTask buildable_tasks = 2; + + // params are parameters provided when creating a workflow. + map params = 3; +} + +message BuildableTask { + // name is a unique name for a task, such as fetch_go_source. The name must be unique across + // all workflow configurations. + string name = 1; + + // depends_on is the name of a task this task depends on. Artifacts from the depends_on task will be available + // to this task. + string depends_on = 2; + + // task_status is the current status of a task. + TaskStatus status = 3; + + // artifact_url is an optional URL to an artifact published by this task. + string artifact_url = 4; + + // git_source is an optional configuration for which git source to fetch. + GitSource git_source = 5; + + // task_type is a unique type for a task, such as FetchGerritSource. Types are used by task runners to identify + // how to execute a task. + string task_type = 6; +} + +message GitSource { + string url = 1; + string ref = 2; +} + +enum TaskStatus { + TASK_STATUS_UNKNOWN = 0; + TASK_STATUS_CREATED = 1; + TASK_STATUS_STARTED = 2; +} diff --git a/cmd/relui/store.go b/cmd/relui/store.go new file mode 100644 index 0000000000..09fd362428 --- /dev/null +++ b/cmd/relui/store.go @@ -0,0 +1,42 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "sync" + + reluipb "golang.org/x/build/cmd/relui/protos" +) + +// store is a persistence adapter for saving data. +type store interface { + GetWorkflows() []*reluipb.Workflow + AddWorkflow(workflow *reluipb.Workflow) error +} + +var _ store = (*memoryStore)(nil) + +// memoryStore is a non-durable implementation of store that keeps everything in memory. +type memoryStore struct { + mut sync.Mutex + workflows []*reluipb.Workflow +} + +// AddWorkflow adds a workflow to the store. +func (m *memoryStore) AddWorkflow(w *reluipb.Workflow) error { + m.mut.Lock() + defer m.mut.Unlock() + m.workflows = append(m.workflows, w) + return nil +} + +// GetWorkflows returns all workflows stored. +// +// TODO(golang.org/issue/40279) - clone workflows if they're ever mutated. +func (m *memoryStore) GetWorkflows() []*reluipb.Workflow { + m.mut.Lock() + defer m.mut.Unlock() + return m.workflows +} diff --git a/cmd/relui/templates/home.html b/cmd/relui/templates/home.html index 7cd9342a16..c86883095a 100644 --- a/cmd/relui/templates/home.html +++ b/cmd/relui/templates/home.html @@ -12,12 +12,12 @@

Workflows