mirror of
https://github.com/peridotbuild/peridot.git
synced 2024-10-08 08:54:12 +00:00
Remove mothership from this repo in preparation of OpenELA move
This commit is contained in:
parent
f759b86f85
commit
943a0d60bd
27
tools/mothership/BUILD
vendored
27
tools/mothership/BUILD
vendored
@ -1,27 +0,0 @@
|
||||
# Copyright 2023 Peridot Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
load("//devtools/taskrunner2:defs.bzl", "taskrunner2")
|
||||
|
||||
taskrunner2(
|
||||
name = "mothership",
|
||||
dev_frontend_flags = True,
|
||||
targets = [
|
||||
"//devtools/devtemporal",
|
||||
"//devtools/devdex",
|
||||
],
|
||||
watch_targets = [
|
||||
"//tools/mothership/cmd/mship_dev",
|
||||
],
|
||||
)
|
74
tools/mothership/admin/rpc/BUILD
vendored
74
tools/mothership/admin/rpc/BUILD
vendored
@ -1,74 +0,0 @@
|
||||
# Copyright 2023 Peridot Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "rpc",
|
||||
srcs = [
|
||||
"operation.go",
|
||||
"rescue.go",
|
||||
"retract.go",
|
||||
"rpc.go",
|
||||
"worker.go",
|
||||
],
|
||||
importpath = "go.resf.org/peridot/tools/mothership/admin/rpc",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//base/go",
|
||||
"//third_party/googleapis/google/longrunning:longrunning_go_proto",
|
||||
"//tools/mothership/db",
|
||||
"//tools/mothership/proto/admin/v1:pb",
|
||||
"//tools/mothership/proto/v1:pb",
|
||||
"//tools/mothership/worker_server",
|
||||
"//vendor/go.ciq.dev/pika",
|
||||
"//vendor/go.temporal.io/api/enums/v1:enums",
|
||||
"//vendor/go.temporal.io/api/serviceerror",
|
||||
"//vendor/go.temporal.io/api/workflowservice/v1:workflowservice",
|
||||
"//vendor/go.temporal.io/sdk/client",
|
||||
"@go_googleapis//google/rpc:code_go_proto",
|
||||
"@go_googleapis//google/rpc:errdetails_go_proto",
|
||||
"@go_googleapis//google/rpc:status_go_proto",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//codes",
|
||||
"@org_golang_google_grpc//status",
|
||||
"@org_golang_google_protobuf//types/known/anypb",
|
||||
"@org_golang_google_protobuf//types/known/emptypb",
|
||||
"@org_golang_google_protobuf//types/known/timestamppb",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "rpc_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"main_test.go",
|
||||
"worker_test.go",
|
||||
],
|
||||
embed = [":rpc"],
|
||||
deps = [
|
||||
"//base/go",
|
||||
"//tools/mothership/db",
|
||||
"//tools/mothership/migrations",
|
||||
"//tools/mothership/proto/admin/v1:pb",
|
||||
"//vendor/github.com/stretchr/testify/require",
|
||||
"//vendor/github.com/testcontainers/testcontainers-go",
|
||||
"//vendor/github.com/testcontainers/testcontainers-go/modules/postgres",
|
||||
"//vendor/github.com/testcontainers/testcontainers-go/wait",
|
||||
"//vendor/go.temporal.io/sdk/client",
|
||||
"@org_golang_google_grpc//codes",
|
||||
"@org_golang_google_grpc//metadata",
|
||||
"@org_golang_google_grpc//status",
|
||||
],
|
||||
)
|
@ -1,104 +0,0 @@
|
||||
// Copyright 2023 Peridot Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mothershipadmin_rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/modules/postgres"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
base "go.resf.org/peridot/base/go"
|
||||
"go.resf.org/peridot/tools/mothership/migrations"
|
||||
"go.temporal.io/sdk/client"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
s *Server
|
||||
userInfo base.UserInfo
|
||||
)
|
||||
|
||||
type fakeTemporalClient struct {
|
||||
client.Client
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Create temporary file
|
||||
dir, err := os.MkdirTemp("", "test-db-*")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
scripts, err := base.EmbedFSToOSFS(dir, mothership_migrations.UpSQLs, ".")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
pgContainer, err := postgres.RunContainer(
|
||||
ctx,
|
||||
testcontainers.WithImage("postgres:15.3-alpine"),
|
||||
postgres.WithInitScripts(scripts...),
|
||||
postgres.WithDatabase("mshiptest"),
|
||||
postgres.WithUsername("postgres"),
|
||||
postgres.WithPassword("postgres"),
|
||||
testcontainers.WithWaitStrategy(
|
||||
wait.
|
||||
ForLog("database system is ready to accept connections").
|
||||
WithOccurrence(2).WithStartupTimeout(5*time.Second),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer pgContainer.Terminate(ctx)
|
||||
|
||||
connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
db, err := base.NewDB(connStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
provider := base.NewTestOidcProvider(&userInfo)
|
||||
|
||||
interceptorDetails := &base.OidcInterceptorDetails{
|
||||
Provider: provider,
|
||||
Group: "",
|
||||
}
|
||||
s, err = NewServer(db, &fakeTemporalClient{}, interceptorDetails)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func testContext() context.Context {
|
||||
mdMap := map[string]string{}
|
||||
if userInfo != nil {
|
||||
mdMap["authorization"] = "bearer " + userInfo.Subject()
|
||||
}
|
||||
md := metadata.New(mdMap)
|
||||
ctx := metadata.NewIncomingContext(context.Background(), md)
|
||||
return context.WithValue(ctx, "user", userInfo)
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
// Copyright 2023 Peridot Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mothershipadmin_rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
base "go.resf.org/peridot/base/go"
|
||||
mshipadminpb "go.resf.org/peridot/tools/mothership/admin/pb"
|
||||
mothershippb "go.resf.org/peridot/tools/mothership/pb"
|
||||
v11 "go.temporal.io/api/enums/v1"
|
||||
"go.temporal.io/api/serviceerror"
|
||||
"go.temporal.io/api/workflowservice/v1"
|
||||
"google.golang.org/genproto/googleapis/longrunning"
|
||||
rpccode "google.golang.org/genproto/googleapis/rpc/code"
|
||||
rpcstatus "google.golang.org/genproto/googleapis/rpc/status"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func (s *Server) describeWorkflowToOperation(ctx context.Context, res *workflowservice.DescribeWorkflowExecutionResponse) (*longrunning.Operation, error) {
|
||||
if res.WorkflowExecutionInfo == nil {
|
||||
return nil, status.Error(codes.NotFound, "workflow not found")
|
||||
}
|
||||
if res.WorkflowExecutionInfo.Execution == nil {
|
||||
return nil, status.Error(codes.NotFound, "workflow not found")
|
||||
}
|
||||
|
||||
op := &longrunning.Operation{
|
||||
Name: res.WorkflowExecutionInfo.Execution.WorkflowId,
|
||||
}
|
||||
|
||||
// If the workflow is not running, we can mark the operation as done
|
||||
if res.WorkflowExecutionInfo.Status != v11.WORKFLOW_EXECUTION_STATUS_RUNNING {
|
||||
op.Done = true
|
||||
}
|
||||
|
||||
// Add metadata
|
||||
rpmMetadata := &mshipadminpb.RetractEntryMetadata{
|
||||
StartTime: nil,
|
||||
EndTime: nil,
|
||||
}
|
||||
st := res.WorkflowExecutionInfo.GetStartTime()
|
||||
if st != nil {
|
||||
rpmMetadata.StartTime = timestamppb.New(*st)
|
||||
}
|
||||
|
||||
et := res.WorkflowExecutionInfo.GetCloseTime()
|
||||
if et != nil {
|
||||
rpmMetadata.EndTime = timestamppb.New(*et)
|
||||
}
|
||||
|
||||
rpmMetadataAny, err := anypb.New(rpmMetadata)
|
||||
if err != nil {
|
||||
return op, nil
|
||||
}
|
||||
op.Metadata = rpmMetadataAny
|
||||
|
||||
// If completed, add result
|
||||
// If failed, add error
|
||||
if res.WorkflowExecutionInfo.Status == v11.WORKFLOW_EXECUTION_STATUS_COMPLETED {
|
||||
// Complete, we need to get the result using GetWorkflow
|
||||
run := s.temporal.GetWorkflow(ctx, op.Name, "")
|
||||
|
||||
var res mothershippb.ProcessRPMResponse
|
||||
if err := run.Get(ctx, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resAny, err := anypb.New(&res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
op.Result = &longrunning.Operation_Response{Response: resAny}
|
||||
} else if res.WorkflowExecutionInfo.Status == v11.WORKFLOW_EXECUTION_STATUS_FAILED {
|
||||
// Failed, we need to get the error using GetWorkflow
|
||||
run := s.temporal.GetWorkflow(ctx, op.Name, "")
|
||||
err := run.Get(ctx, nil)
|
||||
// No error so return with a generic error
|
||||
if err == nil {
|
||||
op.Result = &longrunning.Operation_Error{
|
||||
Error: &rpcstatus.Status{
|
||||
Code: int32(rpccode.Code_INTERNAL),
|
||||
Message: "workflow failed",
|
||||
},
|
||||
}
|
||||
return op, nil
|
||||
}
|
||||
|
||||
// Error, so return with the error
|
||||
op.Result = &longrunning.Operation_Error{
|
||||
Error: &rpcstatus.Status{
|
||||
Code: int32(rpccode.Code_FAILED_PRECONDITION),
|
||||
Message: err.Error(),
|
||||
},
|
||||
}
|
||||
} else if res.WorkflowExecutionInfo.Status == v11.WORKFLOW_EXECUTION_STATUS_CANCELED {
|
||||
// Error, so return with the error
|
||||
op.Result = &longrunning.Operation_Error{
|
||||
Error: &rpcstatus.Status{
|
||||
Code: int32(rpccode.Code_CANCELLED),
|
||||
Message: "workflow canceled",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return op, nil
|
||||
}
|
||||
|
||||
func (s *Server) getOperation(ctx context.Context, name string) (*longrunning.Operation, error) {
|
||||
res, err := s.temporal.DescribeWorkflowExecution(ctx, name, "")
|
||||
if err != nil {
|
||||
if _, ok := err.(*serviceerror.NotFound); ok {
|
||||
return nil, status.Error(codes.NotFound, "workflow not found")
|
||||
}
|
||||
|
||||
// Log error, but user doesn't need to know about it
|
||||
base.LogErrorf("failed to describe workflow: %v", err)
|
||||
return &longrunning.Operation{
|
||||
Name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return s.describeWorkflowToOperation(ctx, res)
|
||||
}
|
||||
|
||||
func (s *Server) GetOperation(ctx context.Context, req *longrunning.GetOperationRequest) (*longrunning.Operation, error) {
|
||||
// Get from Temporal. We don't care about long term storage, so we don't
|
||||
// need to store the operation in the database.
|
||||
return s.getOperation(ctx, req.Name)
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
// Copyright 2023 Peridot Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mothershipadmin_rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
base "go.resf.org/peridot/base/go"
|
||||
mshipadminpb "go.resf.org/peridot/tools/mothership/admin/pb"
|
||||
mothership_db "go.resf.org/peridot/tools/mothership/db"
|
||||
mothershippb "go.resf.org/peridot/tools/mothership/pb"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *Server) RescueEntryImport(ctx context.Context, req *mshipadminpb.RescueEntryImportRequest) (*emptypb.Empty, error) {
|
||||
// First make sure an entry with the given name exists.
|
||||
entry, err := base.Q[mothership_db.Entry](s.db).F("name", req.Name).GetOrNil()
|
||||
if err != nil {
|
||||
base.LogErrorf("failed to get entry: %v", err)
|
||||
return nil, status.Error(codes.Internal, "failed to get entry")
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
return nil, status.Error(codes.NotFound, "entry not found")
|
||||
}
|
||||
|
||||
// Make sure the entry is on hold.
|
||||
if entry.State != mothershippb.Entry_ON_HOLD {
|
||||
return nil, status.Error(codes.FailedPrecondition, "entry is not on hold")
|
||||
}
|
||||
|
||||
// If on hold, then signal the workflow to continue.
|
||||
err = s.temporal.SignalWorkflow(ctx, "operations/"+entry.Sha256Sum, "", "rescue", true)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "already completed") {
|
||||
// For some reason the entry got stuck in a weird state.
|
||||
// Let's just set the state to FAILED.
|
||||
entry.State = mothershippb.Entry_FAILED
|
||||
err = base.Q[mothership_db.Entry](s.db).U(entry)
|
||||
if err != nil {
|
||||
base.LogErrorf("failed to update entry: %v", err)
|
||||
return nil, status.Error(codes.Internal, "failed to update entry")
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
base.LogErrorf("failed to signal workflow: %v", err)
|
||||
return nil, status.Error(codes.Internal, "failed to signal workflow")
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
// Copyright 2023 Peridot Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mothershipadmin_rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
base "go.resf.org/peridot/base/go"
|
||||
mshipadminpb "go.resf.org/peridot/tools/mothership/admin/pb"
|
||||
mothership_worker_server "go.resf.org/peridot/tools/mothership/worker_server"
|
||||
enumspb "go.temporal.io/api/enums/v1"
|
||||
"go.temporal.io/sdk/client"
|
||||
"google.golang.org/genproto/googleapis/longrunning"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *Server) RetractEntry(ctx context.Context, req *mshipadminpb.RetractEntryRequest) (*longrunning.Operation, error) {
|
||||
startWorkflowOpts := client.StartWorkflowOptions{
|
||||
ID: "operations/retract/" + req.Name,
|
||||
WorkflowExecutionErrorWhenAlreadyStarted: true,
|
||||
WorkflowIDReusePolicy: enumspb.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY,
|
||||
}
|
||||
|
||||
// Submit to Temporal
|
||||
run, err := s.temporal.ExecuteWorkflow(
|
||||
context.Background(),
|
||||
startWorkflowOpts,
|
||||
mothership_worker_server.RetractEntryWorkflow,
|
||||
req.Name,
|
||||
)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "is already running") {
|
||||
return nil, status.Error(codes.AlreadyExists, "entry is already running")
|
||||
}
|
||||
base.LogErrorf("failed to start workflow: %v", err)
|
||||
return nil, status.Error(codes.Internal, "failed to start workflow")
|
||||
}
|
||||
|
||||
return s.getOperation(ctx, run.GetID())
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
// Copyright 2023 Peridot Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mothershipadmin_rpc
|
||||
|
||||
import (
|
||||
base "go.resf.org/peridot/base/go"
|
||||
mshipadminpb "go.resf.org/peridot/tools/mothership/admin/pb"
|
||||
"go.temporal.io/sdk/client"
|
||||
"google.golang.org/genproto/googleapis/longrunning"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
base.GRPCServer
|
||||
|
||||
mshipadminpb.UnimplementedMshipAdminServer
|
||||
longrunning.UnimplementedOperationsServer
|
||||
|
||||
db *base.DB
|
||||
temporal client.Client
|
||||
}
|
||||
|
||||
func NewServer(db *base.DB, temporalClient client.Client, oidcInterceptorDetails *base.OidcInterceptorDetails, opts ...base.GRPCServerOption) (*Server, error) {
|
||||
oidcInterceptor, err := base.OidcGrpcInterceptor(oidcInterceptorDetails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts = append(opts, base.WithUnaryInterceptors(oidcInterceptor))
|
||||
grpcServer, err := base.NewGRPCServer(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Server{
|
||||
GRPCServer: *grpcServer,
|
||||
db: db,
|
||||
temporal: temporalClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
s.RegisterService(func(server *grpc.Server) {
|
||||
longrunning.RegisterOperationsServer(server, s)
|
||||
mshipadminpb.RegisterMshipAdminServer(server, s)
|
||||
})
|
||||
if err := s.GatewayEndpoints(
|
||||
longrunning.RegisterOperationsHandler,
|
||||
mshipadminpb.RegisterMshipAdminHandler,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.GRPCServer.Start()
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
// Copyright 2023 Peridot Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mothershipadmin_rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.ciq.dev/pika"
|
||||
base "go.resf.org/peridot/base/go"
|
||||
mshipadminpb "go.resf.org/peridot/tools/mothership/admin/pb"
|
||||
mothership_db "go.resf.org/peridot/tools/mothership/db"
|
||||
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *Server) GetWorker(_ context.Context, req *mshipadminpb.GetWorkerRequest) (*mshipadminpb.Worker, error) {
|
||||
worker, err := base.Q[mothership_db.Worker](s.db).F("name", req.Name).GetOrNil()
|
||||
if err != nil {
|
||||
base.LogErrorf("failed to get worker: %v", err)
|
||||
return nil, status.Error(codes.Internal, "failed to get worker")
|
||||
}
|
||||
|
||||
if worker == nil {
|
||||
return nil, status.Error(codes.NotFound, "worker not found")
|
||||
}
|
||||
|
||||
return worker.ToPB(), nil
|
||||
}
|
||||
|
||||
func (s *Server) ListWorkers(_ context.Context, req *mshipadminpb.ListWorkersRequest) (*mshipadminpb.ListWorkersResponse, error) {
|
||||
aipOptions := pika.ProtoReflect(&mshipadminpb.Worker{})
|
||||
|
||||
page, nt, err := base.Q[mothership_db.Worker](s.db).GetPage(req, aipOptions)
|
||||
if err != nil {
|
||||
base.LogErrorf("failed to get worker page: %v", err)
|
||||
return nil, status.Error(codes.Internal, "failed to get worker page")
|
||||
}
|
||||
|
||||
return &mshipadminpb.ListWorkersResponse{
|
||||
Workers: base.SliceToPB[*mshipadminpb.Worker, *mothership_db.Worker](page),
|
||||
NextPageToken: nt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) CreateWorker(_ context.Context, req *mshipadminpb.CreateWorkerRequest) (*mshipadminpb.Worker, error) {
|
||||
// Verify that the id is at least 4 characters long.
|
||||
if len(req.WorkerId) < 4 {
|
||||
return nil, status.Error(codes.InvalidArgument, "worker id must be at least 4 characters long")
|
||||
}
|
||||
|
||||
// Create the worker.
|
||||
name := base.NameGen("workers")
|
||||
worker := &mothership_db.Worker{
|
||||
Name: name,
|
||||
WorkerID: req.WorkerId,
|
||||
ApiSecret: base.NameGen(name),
|
||||
}
|
||||
|
||||
err := base.Q[mothership_db.Worker](s.db).Create(worker)
|
||||
if err != nil {
|
||||
// if the error is a duplicate key error, return an already exists error.
|
||||
if strings.Contains(err.Error(), "duplicate key") {
|
||||
st, _ := status.
|
||||
New(codes.AlreadyExists, "worker already exists").
|
||||
WithDetails(&errdetails.LocalizedMessage{
|
||||
Locale: "en-US",
|
||||
Message: "Worker with given ID already exists.",
|
||||
})
|
||||
|
||||
return nil, st.Err()
|
||||
}
|
||||
|
||||
base.LogErrorf("failed to create worker: %v", err)
|
||||
return nil, status.Error(codes.Internal, "failed to create worker")
|
||||
}
|
||||
pb := worker.ToPB()
|
||||
pb.ApiSecret = worker.ApiSecret
|
||||
|
||||
return pb, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteWorker(_ context.Context, req *mshipadminpb.DeleteWorkerRequest) (*emptypb.Empty, error) {
|
||||
worker, err := base.Q[mothership_db.Worker](s.db).F("name", req.Name).GetOrNil()
|
||||
if err != nil {
|
||||
base.LogErrorf("failed to get worker: %v", err)
|
||||
return nil, status.Error(codes.Internal, "failed to get worker")
|
||||
}
|
||||
|
||||
if worker == nil {
|
||||
return nil, status.Error(codes.NotFound, "worker not found")
|
||||
}
|
||||
|
||||
err = base.Q[mothership_db.Worker](s.db).D(worker)
|
||||
if err != nil {
|
||||
base.LogErrorf("failed to delete worker: %v", err)
|
||||
return nil, status.Error(codes.Internal, "failed to delete worker")
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
// Copyright 2023 Peridot Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mothershipadmin_rpc
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
base "go.resf.org/peridot/base/go"
|
||||
mshipadminpb "go.resf.org/peridot/tools/mothership/admin/pb"
|
||||
mothership_db "go.resf.org/peridot/tools/mothership/db"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetWorker_Empty(t *testing.T) {
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
worker, err := s.GetWorker(testContext(), &mshipadminpb.GetWorkerRequest{})
|
||||
require.NotNil(t, err)
|
||||
require.Nil(t, worker)
|
||||
expectedErr := status.Error(codes.NotFound, "worker not found")
|
||||
require.Equal(t, expectedErr.Error(), err.Error())
|
||||
}
|
||||
|
||||
func TestGetWorker_One(t *testing.T) {
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Create(&mothership_db.Worker{
|
||||
Name: "test",
|
||||
WorkerID: "test-id",
|
||||
ApiSecret: "secret",
|
||||
}))
|
||||
defer func() {
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
}()
|
||||
|
||||
worker, err := s.GetWorker(testContext(), &mshipadminpb.GetWorkerRequest{
|
||||
Name: "test",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "test", worker.Name)
|
||||
require.Equal(t, "test-id", worker.WorkerId)
|
||||
require.Empty(t, worker.ApiSecret)
|
||||
}
|
||||
|
||||
func TestListWorkers_Empty(t *testing.T) {
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
workers, err := s.ListWorkers(testContext(), &mshipadminpb.ListWorkersRequest{})
|
||||
require.Nil(t, err)
|
||||
require.Empty(t, workers.Workers)
|
||||
}
|
||||
|
||||
func TestListWorkers_One(t *testing.T) {
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Create(&mothership_db.Worker{
|
||||
Name: "test",
|
||||
WorkerID: "test-id",
|
||||
ApiSecret: "secret",
|
||||
}))
|
||||
defer func() {
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
}()
|
||||
|
||||
workers, err := s.ListWorkers(testContext(), &mshipadminpb.ListWorkersRequest{})
|
||||
require.Nil(t, err)
|
||||
require.Len(t, workers.Workers, 1)
|
||||
require.Equal(t, "test", workers.Workers[0].Name)
|
||||
require.Equal(t, "test-id", workers.Workers[0].WorkerId)
|
||||
require.Empty(t, workers.Workers[0].ApiSecret)
|
||||
}
|
||||
|
||||
func TestCreateWorker(t *testing.T) {
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
worker, err := s.CreateWorker(testContext(), &mshipadminpb.CreateWorkerRequest{
|
||||
WorkerId: "test-id",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "test-id", worker.WorkerId)
|
||||
require.NotEmpty(t, worker.Name)
|
||||
require.NotEmpty(t, worker.ApiSecret)
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
}
|
||||
|
||||
func TestCreateWorker_Duplicate(t *testing.T) {
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
_, err := s.CreateWorker(testContext(), &mshipadminpb.CreateWorkerRequest{
|
||||
WorkerId: "test-id",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
_, err = s.CreateWorker(testContext(), &mshipadminpb.CreateWorkerRequest{
|
||||
WorkerId: "test-id",
|
||||
})
|
||||
require.NotNil(t, err)
|
||||
require.Equal(t, codes.AlreadyExists.String(), status.Code(err).String())
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
}
|
||||
|
||||
func TestCreateWorker_ShortID(t *testing.T) {
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
_, err := s.CreateWorker(testContext(), &mshipadminpb.CreateWorkerRequest{
|
||||
WorkerId: "id",
|
||||
})
|
||||
require.NotNil(t, err)
|
||||
require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String())
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
}
|
||||
|
||||
func TestDeleteWorker(t *testing.T) {
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
worker, err := s.CreateWorker(testContext(), &mshipadminpb.CreateWorkerRequest{
|
||||
WorkerId: "test-id",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
_, err = s.DeleteWorker(testContext(), &mshipadminpb.DeleteWorkerRequest{
|
||||
Name: worker.Name,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
_, err = s.GetWorker(testContext(), &mshipadminpb.GetWorkerRequest{
|
||||
Name: worker.Name,
|
||||
})
|
||||
require.NotNil(t, err)
|
||||
require.Equal(t, codes.NotFound.String(), status.Code(err).String())
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
}
|
||||
|
||||
func TestDeleteWorker_NotFound(t *testing.T) {
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
_, err := s.DeleteWorker(testContext(), &mshipadminpb.DeleteWorkerRequest{
|
||||
Name: "test",
|
||||
})
|
||||
require.NotNil(t, err)
|
||||
require.Equal(t, codes.NotFound.String(), status.Code(err).String())
|
||||
require.Nil(t, base.Q[mothership_db.Worker](s.db).Delete())
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/**
|
||||
* Copyright 2023 Peridot Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Box from '@mui/material/Box';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Button from '@mui/material/Button';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
|
||||
import EngineeringIcon from '@mui/icons-material/Engineering';
|
||||
import ImportExportIcon from '@mui/icons-material/ImportExport';
|
||||
|
||||
import { Drawer } from 'base/ts/mui/Drawer';
|
||||
|
||||
import { CreateWorker } from 'tools/mothership/admin/ui/CreateWorker';
|
||||
import { GetWorker } from 'tools/mothership/admin/ui/GetWorker';
|
||||
|
||||
import { Entries } from './Entries';
|
||||
import { GetEntry } from './GetEntry';
|
||||
import { Workers } from './Workers';
|
||||
|
||||
export const App = () => {
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<AppBar
|
||||
elevation={5}
|
||||
position="fixed"
|
||||
sx={{ zIndex: (theme: Theme) => theme.zIndex.drawer + 1 }}
|
||||
>
|
||||
<Toolbar variant="dense">
|
||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
||||
Mship Admin{window.__beta__ ? ' (beta)' : ''}
|
||||
</Typography>
|
||||
<Box sx={{ flexGrow: 1, textAlign: 'right' }}>
|
||||
<Button className="native-link" href="/admin/auth/oidc/logout" variant="primary">
|
||||
Logout
|
||||
</Button>
|
||||
<Button className="native-link" href="/" variant="primary">
|
||||
Go back to Mship
|
||||
</Button>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Drawer
|
||||
sections={[
|
||||
{
|
||||
links: [
|
||||
{ text: 'Workers', href: '/workers', icon: <EngineeringIcon /> },
|
||||
{ text: 'Entries', href: '/entries', icon: <ImportExportIcon /> },
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Box component="main" sx={{ flexGrow: 1 }}>
|
||||
<Toolbar variant="dense" />
|
||||
<Routes>
|
||||
<Route index element={<Navigate to="/workers" replace />} />
|
||||
<Route path="/workers">
|
||||
<Route index element={<Workers />} />
|
||||
<Route path="create" element={<CreateWorker />} />
|
||||
<Route path=":name" element={<GetWorker />} />
|
||||
</Route>
|
||||
<Route path="/entries">
|
||||
<Route index element={<Entries />} />
|
||||
<Route path=":name" element={<GetEntry />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
38
tools/mothership/admin/ui/BUILD
vendored
38
tools/mothership/admin/ui/BUILD
vendored
@ -1,38 +0,0 @@
|
||||
# Copyright 2023 Peridot Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
load("//tools/build_rules/ui_bundle:defs.bzl", "ui_bundle")
|
||||
|
||||
ui_bundle(
|
||||
name = "bundle",
|
||||
deps = [
|
||||
"//:node_modules/@mui/icons-material",
|
||||
"//:node_modules/@mui/material",
|
||||
"//base/ts/mui",
|
||||
"//tools/mothership/proto/admin/v1:mshipadminpb_ts_proto",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "ui",
|
||||
srcs = ["ui.go"],
|
||||
# keep
|
||||
embedsrcs = [
|
||||
":bundle", # keep
|
||||
],
|
||||
importpath = "go.resf.org/peridot/tools/mothership/admin/ui",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//base/go"],
|
||||
)
|
@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright 2023 Peridot Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Box from '@mui/material/Box';
|
||||
import Divider from '@mui/material/Divider';
|
||||
|
||||
import { NewResource } from 'base/ts/mui/NewResource';
|
||||
import { reqap } from 'base/ts/reqap';
|
||||
|
||||
import { V1Worker } from 'bazel-bin/tools/mothership/proto/admin/v1/mshipadminpb_ts_proto_gen';
|
||||
import { mshipAdminApi } from 'tools/mothership/admin/ui/api';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
|
||||
export const CreateWorker = () => {
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ p: 1.5 }}>Create a new worker</Box>
|
||||
<Divider />
|
||||
<Box sx={{ p: 1.5, mt: 2 }}>
|
||||
<NewResource
|
||||
fields={[
|
||||
{
|
||||
key: 'workerId',
|
||||
label: 'Worker ID',
|
||||
subtitle:
|
||||
'This ID will be used to uniquely identify this worker.',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
save={(x: V1Worker) =>
|
||||
reqap(
|
||||
mshipAdminApi.createWorker({ body: { workerId: x.workerId!! } }),
|
||||
)
|
||||
}
|
||||
showDialog={(resource: V1Worker) => (
|
||||
<>
|
||||
<DialogTitle>Worker created</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
The worker API secret is <code>{resource.apiSecret}</code>.
|
||||
<br /><br />
|
||||
This is the only time it will be shown, so make sure to save
|
||||
it somewhere safe.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright 2023 Peridot Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
import { ResourceTable } from 'base/ts/mui/ResourceTable';
|
||||
import { srpmArchiverApi } from 'tools/mothership/admin/ui/api';
|
||||
import {
|
||||
V1ListEntriesResponse,
|
||||
V1Entry,
|
||||
} from 'bazel-bin/tools/mothership/proto/v1/mothershippb_ts_proto_gen';
|
||||
import { reqap } from 'base/ts/reqap';
|
||||
|
||||
export const Entries = () => {
|
||||
return (
|
||||
<Box sx={{p: 1.5, px: 3, width: '100%'}}>
|
||||
<ResourceTable<V1Entry>
|
||||
load={(pageSize: number, pageToken?: string) => reqap(srpmArchiverApi.listEntries({
|
||||
pageSize: pageSize,
|
||||
pageToken: pageToken,
|
||||
}))}
|
||||
transform={((response: V1ListEntriesResponse) => response.entries || [])}
|
||||
fields={[
|
||||
{ key: 'name', label: 'Entry Name' },
|
||||
{ key: 'entryId', label: 'Entry ID' },
|
||||
{ key: 'state', label: 'State' },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -1,142 +0,0 @@
|
||||
/**
|
||||
* Copyright 2023 Peridot Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { ResourceView } from 'base/ts/mui/ResourceView';
|
||||
import { reqap } from 'base/ts/reqap';
|
||||
|
||||
import {
|
||||
EntryState,
|
||||
V1Entry,
|
||||
} from 'bazel-bin/tools/mothership/proto/v1/mothershippb_ts_proto_gen';
|
||||
import Box from '@mui/material/Box';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import { mshipAdminApi, srpmArchiverApi } from 'tools/mothership/admin/ui/api';
|
||||
import Button from '@mui/material/Button';
|
||||
|
||||
export const GetEntry = () => {
|
||||
const params = useParams();
|
||||
const [resource, setResource] = React.useState<V1Entry | undefined | null>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
// Load the resource
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
const [res, err] = await reqap(
|
||||
srpmArchiverApi.getEntry({
|
||||
name1: `entries/${params.name}`,
|
||||
}),
|
||||
);
|
||||
|
||||
if (err) {
|
||||
setResource(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setResource(res);
|
||||
})().then();
|
||||
}, []);
|
||||
|
||||
// Rescue the entry (call API)
|
||||
const rescueEntry = async () => {
|
||||
const [res, err] = await reqap(
|
||||
mshipAdminApi.rescueEntryImport({
|
||||
name: `entries/${params.name}`,
|
||||
}),
|
||||
);
|
||||
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
// Retract the entry (call API)
|
||||
const retractEntry = async () => {
|
||||
const [res, err] = await reqap(
|
||||
mshipAdminApi.retractEntry({
|
||||
name: `entries/${params.name}`,
|
||||
}),
|
||||
);
|
||||
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
px: 1.5,
|
||||
height: '48px',
|
||||
display: 'flex',
|
||||
justifyContent: 'justify-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<span>entries/{params.name}</span>
|
||||
{resource && resource.state == EntryState.OnHold && (
|
||||
<Button
|
||||
sx={{ ml: 'auto', textAlign: 'right' }}
|
||||
variant="outlined"
|
||||
onClick={rescueEntry}
|
||||
>
|
||||
Rescue
|
||||
</Button>
|
||||
)}
|
||||
{resource && resource.state == EntryState.Archived && (
|
||||
<Button
|
||||
sx={{ ml: 'auto', textAlign: 'right' }}
|
||||
variant="outlined"
|
||||
color="error"
|
||||
onClick={retractEntry}
|
||||
>
|
||||
Retract
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box sx={{ p: 1.5 }}>
|
||||
<ResourceView
|
||||
resource={resource}
|
||||
fields={[
|
||||
{ key: 'entryId', label: 'Entry ID' },
|
||||
{ key: 'createTime', label: 'Created' },
|
||||
{ key: 'osRelease', label: 'OS Release' },
|
||||
{ key: 'sha256Sum', label: 'SHA256 Sum' },
|
||||
{ key: 'repository', label: 'Repository' },
|
||||
{ key: 'workerId', label: 'Worker ID' },
|
||||
{ key: 'commitUri', label: 'Commit URI', linkToSelf: true },
|
||||
{ key: 'commitHash', label: 'Commit Hash' },
|
||||
{ key: 'state', label: 'State' },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
{resource && resource.state == EntryState.OnHold && (
|
||||
<Box sx={{ p: 1.5 }}>
|
||||
<code>{resource.errorMessage}</code>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -1,121 +0,0 @@
|
||||
/**
|
||||
* Copyright 2023 Peridot Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { ResourceView } from 'base/ts/mui/ResourceView';
|
||||
import { reqap } from 'base/ts/reqap';
|
||||
|
||||
import { mshipAdminApi } from 'tools/mothership/admin/ui/api';
|
||||
|
||||
import { V1Worker } from 'bazel-bin/tools/mothership/proto/admin/v1/mshipadminpb_ts_proto_gen';
|
||||
import Box from '@mui/material/Box';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
|
||||
export const GetWorker = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const params = useParams();
|
||||
const [resource, setResource] = React.useState<V1Worker | undefined | null>(
|
||||
undefined,
|
||||
);
|
||||
const [deleteOpen, setDeleteOpen] = React.useState<boolean>(false);
|
||||
|
||||
// Load the resource
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
const [res, err] = await reqap(
|
||||
mshipAdminApi.getWorker({
|
||||
name: `workers/${params.name}`,
|
||||
}),
|
||||
);
|
||||
|
||||
if (err) {
|
||||
setResource(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setResource(res);
|
||||
})().then();
|
||||
}, []);
|
||||
|
||||
const doDelete = async () => {
|
||||
const [res, err] = await reqap(
|
||||
mshipAdminApi.deleteWorker({
|
||||
name: `workers/${params.name}`,
|
||||
}),
|
||||
);
|
||||
|
||||
if (err) {
|
||||
setDeleteOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Dialog open={deleteOpen}>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete this worker?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDeleteOpen(false)}>Cancel</Button>
|
||||
<Button onClick={doDelete} color="error">Delete</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Box
|
||||
sx={{
|
||||
px: 1.5,
|
||||
height: '48px',
|
||||
display: 'flex',
|
||||
justifyContent: 'justify-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<span>workers/{params.name}</span>
|
||||
<Button
|
||||
sx={{ ml: 'auto', textAlign: 'right' }}
|
||||
variant="outlined"
|
||||
color="error"
|
||||
onClick={() => setDeleteOpen(true)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box sx={{ p: 1.5 }}>
|
||||
<ResourceView
|
||||
resource={resource}
|
||||
fields={[
|
||||
{ key: 'workerId', label: 'Worker ID' },
|
||||
{ key: 'createTime', label: 'Created' },
|
||||
{ key: 'lastCheckinTime', label: 'Last active' },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Copyright 2023 Peridot Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import Button from '@mui/material/Button';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
import { ResourceTable } from 'base/ts/mui/ResourceTable';
|
||||
import { reqap } from 'base/ts/reqap';
|
||||
import { mshipAdminApi } from 'tools/mothership/admin/ui/api';
|
||||
import {
|
||||
V1ListWorkersResponse,
|
||||
V1Worker,
|
||||
} from 'bazel-bin/tools/mothership/proto/admin/v1/mshipadminpb_ts_proto_gen';
|
||||
|
||||
export const Workers = () => {
|
||||
return (
|
||||
<Box sx={{p: 1.5, px: 3, width: '100%'}}>
|
||||
<Box sx={{ mb: 2, ml: 'auto', textAlign: 'right' }}>
|
||||
<Button href="/workers/create" variant="outlined" size="small">
|
||||
Create a new worker
|
||||
</Button>
|
||||
</Box>
|
||||
<ResourceTable<V1Worker>
|
||||
load={(pageSize: number, pageToken?: string) =>
|
||||
reqap(
|
||||
mshipAdminApi.listWorkers({
|
||||
pageSize: pageSize,
|
||||
pageToken: pageToken,
|
||||
}),
|
||||
)
|
||||
}
|
||||
transform={(response: V1ListWorkersResponse) => response.workers || []}
|
||||
fields={[
|
||||
{ key: 'name', label: 'Worker Name' },
|
||||
{ key: 'workerId', label: 'Worker ID' },
|
||||
{ key: 'createTime', label: 'Created' },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright 2023 Peridot Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as mshipAdmin from 'bazel-bin/tools/mothership/proto/admin/v1/mshipadminpb_ts_proto_gen';
|
||||
import * as srpmArchiver from 'bazel-bin/tools/mothership/proto/v1/mothershippb_ts_proto_gen';
|
||||
|
||||
const archiverCfg = new srpmArchiver.Configuration({
|
||||
basePath: '/api',
|
||||
})
|
||||
|
||||
export const srpmArchiverApi = new srpmArchiver.SrpmArchiverApi(archiverCfg);
|
||||
|
||||
const cfg = new mshipAdmin.Configuration({
|
||||
basePath: '/admin/api',
|
||||
})
|
||||
|
||||
export const mshipAdminApi = new mshipAdmin.MshipAdminApi(cfg);
|
@ -1,34 +0,0 @@
|
||||
/**
|
||||
* Copyright 2023 Peridot Authors
|
||||