mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-12-20 17:58:29 +00:00
Add peridotadmin and updateinfo workflow
This commit is contained in:
parent
2470a9df7f
commit
5eec7a5354
26
peridot/admin/v1/BUILD.bazel
Normal file
26
peridot/admin/v1/BUILD.bazel
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "admin",
|
||||||
|
srcs = [
|
||||||
|
"server.go",
|
||||||
|
"updateinfo.go",
|
||||||
|
],
|
||||||
|
importpath = "peridot.resf.org/peridot/admin/v1",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//peridot/builder/v1:builder",
|
||||||
|
"//peridot/db",
|
||||||
|
"//peridot/impl/v1:impl",
|
||||||
|
"//peridot/proto/v1:pb",
|
||||||
|
"//peridot/proto/v1/admin:pb",
|
||||||
|
"//proto:common",
|
||||||
|
"//utils",
|
||||||
|
"//vendor/github.com/sirupsen/logrus",
|
||||||
|
"//vendor/go.temporal.io/sdk/client",
|
||||||
|
"@org_golang_google_grpc//:go_default_library",
|
||||||
|
"@org_golang_google_grpc//codes",
|
||||||
|
"@org_golang_google_grpc//credentials/insecure",
|
||||||
|
"@org_golang_google_grpc//status",
|
||||||
|
],
|
||||||
|
)
|
120
peridot/admin/v1/server.go
Normal file
120
peridot/admin/v1/server.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
// Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
// Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
// may be used to endorse or promote products derived from this software without
|
||||||
|
// specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package peridotadminv1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"go.temporal.io/sdk/client"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
commonpb "peridot.resf.org/common"
|
||||||
|
peridotadminpb "peridot.resf.org/peridot/admin/pb"
|
||||||
|
builderv1 "peridot.resf.org/peridot/builder/v1"
|
||||||
|
peridotdb "peridot.resf.org/peridot/db"
|
||||||
|
peridotimplv1 "peridot.resf.org/peridot/impl/v1"
|
||||||
|
"peridot.resf.org/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
peridotadminpb.UnimplementedPeridotAdminServiceServer
|
||||||
|
|
||||||
|
log *logrus.Logger
|
||||||
|
db peridotdb.Access
|
||||||
|
temporal client.Client
|
||||||
|
temporalWorker *builderv1.Worker
|
||||||
|
}
|
||||||
|
|
||||||
|
var adminUser = &utils.ContextUser{
|
||||||
|
ID: "peridot-errata",
|
||||||
|
AuthToken: "",
|
||||||
|
Name: "Peridot Errata",
|
||||||
|
Email: "releng+errata@rockylinux.org",
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(db peridotdb.Access, c client.Client) (*Server, error) {
|
||||||
|
temporalWorker, err := builderv1.NewWorker(db, c, peridotimplv1.MainTaskQueue, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Server{
|
||||||
|
log: logrus.New(),
|
||||||
|
db: db,
|
||||||
|
temporal: c,
|
||||||
|
temporalWorker: temporalWorker,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) interceptor(ctx context.Context, req interface{}, usi *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||||
|
n := utils.EndInterceptor
|
||||||
|
|
||||||
|
return n(ctx, req, usi, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) serverInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||||
|
n := utils.ServerEndInterceptor
|
||||||
|
|
||||||
|
return n(srv, ss, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Run() {
|
||||||
|
res := utils.NewGRPCServer(
|
||||||
|
&utils.GRPCOptions{
|
||||||
|
DialOptions: []grpc.DialOption{
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
},
|
||||||
|
Interceptor: s.interceptor,
|
||||||
|
ServerInterceptor: s.serverInterceptor,
|
||||||
|
},
|
||||||
|
func(r *utils.Register) {
|
||||||
|
endpoints := []utils.GrpcEndpointRegister{
|
||||||
|
commonpb.RegisterHealthCheckServiceHandlerFromEndpoint,
|
||||||
|
peridotadminpb.RegisterPeridotAdminServiceHandlerFromEndpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
err := endpoint(r.Context, r.Mux, r.Endpoint, r.Options)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Fatalf("could not register handler - %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
func(r *utils.RegisterServer) {
|
||||||
|
commonpb.RegisterHealthCheckServiceServer(r.Server, &utils.HealthServer{})
|
||||||
|
|
||||||
|
peridotadminpb.RegisterPeridotAdminServiceServer(r.Server, s)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
defer res.Cancel()
|
||||||
|
res.WaitGroup.Wait()
|
||||||
|
}
|
99
peridot/admin/v1/updateinfo.go
Normal file
99
peridot/admin/v1/updateinfo.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
// Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
// Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
// may be used to endorse or promote products derived from this software without
|
||||||
|
// specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package peridotadminv1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"go.temporal.io/sdk/client"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
adminpb "peridot.resf.org/peridot/admin/pb"
|
||||||
|
peridotpb "peridot.resf.org/peridot/pb"
|
||||||
|
"peridot.resf.org/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) AddUpdateInformation(ctx context.Context, req *adminpb.AddUpdateInformationRequest) (*peridotpb.AsyncTask, error) {
|
||||||
|
if err := req.ValidateAll(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rollback := true
|
||||||
|
beginTx, err := s.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error(err)
|
||||||
|
return nil, utils.InternalError
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if rollback {
|
||||||
|
_ = beginTx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
tx := s.db.UseTransaction(beginTx)
|
||||||
|
|
||||||
|
task, err := tx.CreateTask(adminUser, "noarch", peridotpb.TaskType_TASK_TYPE_UPDATEINFO, &req.ProjectId, nil)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Errorf("could not create task: %v", err)
|
||||||
|
return nil, utils.InternalError
|
||||||
|
}
|
||||||
|
|
||||||
|
taskProto, err := task.ToProto(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "could not marshal task: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rollback = false
|
||||||
|
err = beginTx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.Internal, "could not save, try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.temporal.ExecuteWorkflow(
|
||||||
|
context.Background(),
|
||||||
|
client.StartWorkflowOptions{
|
||||||
|
ID: task.ID.String(),
|
||||||
|
TaskQueue: "yumrepofs",
|
||||||
|
},
|
||||||
|
s.temporalWorker.WorkflowController.UpdateInfoWorkflow,
|
||||||
|
req,
|
||||||
|
task,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Errorf("could not start sync workflow in AddUpdateInformation: %v", err)
|
||||||
|
_ = s.db.SetTaskStatus(task.ID.String(), peridotpb.TaskStatus_TASK_STATUS_FAILED)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &peridotpb.AsyncTask{
|
||||||
|
TaskId: task.ID.String(),
|
||||||
|
Subtasks: []*peridotpb.Subtask{taskProto},
|
||||||
|
Done: false,
|
||||||
|
}, nil
|
||||||
|
}
|
302
peridot/builder/v1/workflow/updateinfo.go
Normal file
302
peridot/builder/v1/workflow/updateinfo.go
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
// Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
// Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
// Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
// may be used to endorse or promote products derived from this software without
|
||||||
|
// specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package workflow
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"cirello.io/dynamolock"
|
||||||
|
"compress/gzip"
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.temporal.io/sdk/temporal"
|
||||||
|
"go.temporal.io/sdk/workflow"
|
||||||
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
adminpb "peridot.resf.org/peridot/admin/pb"
|
||||||
|
"peridot.resf.org/peridot/db/models"
|
||||||
|
peridotpb "peridot.resf.org/peridot/pb"
|
||||||
|
"peridot.resf.org/peridot/yummeta"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Controller) SetUpdateInfoActivity(ctx context.Context, req *adminpb.AddUpdateInformationRequest) (*adminpb.AddUpdateInformationTask, error) {
|
||||||
|
stopChan := makeHeartbeat(ctx, 10*time.Second)
|
||||||
|
defer func() { stopChan <- true }()
|
||||||
|
|
||||||
|
lock, err := dynamolock.New(
|
||||||
|
c.dynamodb,
|
||||||
|
viper.GetString("dynamodb-table"),
|
||||||
|
dynamolock.WithLeaseDuration(10*time.Second),
|
||||||
|
dynamolock.WithHeartbeatPeriod(3*time.Second),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create dynamolock: %v", err)
|
||||||
|
}
|
||||||
|
defer lock.Close()
|
||||||
|
|
||||||
|
var lockedItem *dynamolock.Lock
|
||||||
|
for {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
lockedItem, err = lock.AcquireLock(
|
||||||
|
req.ProjectId,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
c.log.Errorf("failed to acquire lock: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
didRelease := false
|
||||||
|
releaseLock := func() error {
|
||||||
|
if didRelease {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
lockSuccess, err := lock.ReleaseLock(lockedItem)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error releasing lock: %v", err)
|
||||||
|
}
|
||||||
|
if !lockSuccess {
|
||||||
|
return fmt.Errorf("lost lock before release")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer releaseLock()
|
||||||
|
|
||||||
|
projects, err := c.db.ListProjects(&peridotpb.ProjectFilters{
|
||||||
|
Id: wrapperspb.String(req.ProjectId),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list projects: %v", err)
|
||||||
|
}
|
||||||
|
if len(projects) == 0 {
|
||||||
|
return nil, fmt.Errorf("project not found")
|
||||||
|
}
|
||||||
|
project := projects[0]
|
||||||
|
|
||||||
|
repositories, err := c.db.FindRepositoriesForProject(project.ID.String(), nil, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list repositories: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiBase := "https://apollo.build.resf.org/api/v3/updateinfo"
|
||||||
|
|
||||||
|
for _, repo := range repositories {
|
||||||
|
if repo.Name == "all" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arch := range project.Archs {
|
||||||
|
realProductName := strings.ReplaceAll(req.ProductName, "$arch", arch)
|
||||||
|
apiURL := fmt.Sprintf(
|
||||||
|
"%s/%s/%s/updateinfo.xml",
|
||||||
|
apiBase,
|
||||||
|
url.PathEscape(realProductName),
|
||||||
|
repo.Name,
|
||||||
|
)
|
||||||
|
c.log.Infof("Getting updateinfo %s", apiURL)
|
||||||
|
resp, err := http.Get(apiURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get updateinfo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
|
c.log.Warnf("no updateinfo found for %s/%s", realProductName, repo.Name)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.log.Infof("Got updateinfo for %s/%s", realProductName, repo.Name)
|
||||||
|
|
||||||
|
xmlBytes, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read updateinfo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := sha256.New()
|
||||||
|
|
||||||
|
openSize := len(xmlBytes)
|
||||||
|
_, err = hasher.Write(xmlBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not hash updateinfo: %v", err)
|
||||||
|
}
|
||||||
|
openChecksum := hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
hasher.Reset()
|
||||||
|
|
||||||
|
var gzippedBuf bytes.Buffer
|
||||||
|
w := gzip.NewWriter(&gzippedBuf)
|
||||||
|
_, err = w.Write(xmlBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not gzip encode: %v", err)
|
||||||
|
}
|
||||||
|
_ = w.Close()
|
||||||
|
|
||||||
|
closedSize := len(gzippedBuf.Bytes())
|
||||||
|
_, err = hasher.Write(gzippedBuf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not hash gzipped: %v", err)
|
||||||
|
}
|
||||||
|
closedChecksum := hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
hasher.Reset()
|
||||||
|
updateInfoB64 := base64.StdEncoding.EncodeToString(gzippedBuf.Bytes())
|
||||||
|
|
||||||
|
timestamp := time.Now().Unix()
|
||||||
|
|
||||||
|
newRevisionID := uuid.New()
|
||||||
|
updateInfoPath := fmt.Sprintf("repodata/%s-UPDATEINFO.xml.gz", newRevisionID.String())
|
||||||
|
updateInfoEntry := &yummeta.RepoMdData{
|
||||||
|
Type: "updateinfo",
|
||||||
|
Checksum: &yummeta.RepoMdDataChecksum{
|
||||||
|
Type: "sha256",
|
||||||
|
Value: closedChecksum,
|
||||||
|
},
|
||||||
|
OpenChecksum: &yummeta.RepoMdDataChecksum{
|
||||||
|
Type: "sha256",
|
||||||
|
Value: openChecksum,
|
||||||
|
},
|
||||||
|
Location: &yummeta.RepoMdDataLocation{
|
||||||
|
Href: updateInfoPath,
|
||||||
|
},
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Size: closedSize,
|
||||||
|
OpenSize: openSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
latestRevision, err := c.db.GetLatestActiveRepositoryRevision(repo.ID.String(), arch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get latest active repository revision: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the existing repomd.xml
|
||||||
|
// If updateinfo already exists, replace it
|
||||||
|
// Else append it
|
||||||
|
repomdXml, err := base64.StdEncoding.DecodeString(latestRevision.RepomdXml)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decode repomd xml: %w", err)
|
||||||
|
}
|
||||||
|
var repomdRoot yummeta.RepoMdRoot
|
||||||
|
err = xml.Unmarshal(repomdXml, &repomdRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not unmarshal repomd.xml: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
doesExist := false
|
||||||
|
for i, existingEntry := range repomdRoot.Data {
|
||||||
|
if existingEntry.Type == "updateinfo" {
|
||||||
|
repomdRoot.Data[i] = updateInfoEntry
|
||||||
|
doesExist = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !doesExist {
|
||||||
|
repomdRoot.Data = append(repomdRoot.Data, updateInfoEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-marshal the repomd.xml
|
||||||
|
repomdRoot.Revision = newRevisionID.String()
|
||||||
|
repomdXml, err = xml.Marshal(repomdRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not marshal repomd.xml: %v", err)
|
||||||
|
}
|
||||||
|
repomdXmlB64 := base64.StdEncoding.EncodeToString(repomdXml)
|
||||||
|
|
||||||
|
// Create new revision
|
||||||
|
_, err = c.db.CreateRevisionForRepository(
|
||||||
|
newRevisionID.String(),
|
||||||
|
latestRevision.ProjectRepoId,
|
||||||
|
latestRevision.Arch,
|
||||||
|
repomdXmlB64,
|
||||||
|
latestRevision.PrimaryXml,
|
||||||
|
latestRevision.FilelistsXml,
|
||||||
|
latestRevision.OtherXml,
|
||||||
|
updateInfoB64,
|
||||||
|
latestRevision.ModuleDefaultsYaml,
|
||||||
|
latestRevision.ModulesYaml,
|
||||||
|
latestRevision.GroupsXml,
|
||||||
|
latestRevision.UrlMappings.String(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating new revision: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &adminpb.AddUpdateInformationTask{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) UpdateInfoWorkflow(ctx workflow.Context, req *adminpb.AddUpdateInformationRequest, task *models.Task) (*adminpb.AddUpdateInformationTask, error) {
|
||||||
|
var ret adminpb.AddUpdateInformationTask
|
||||||
|
|
||||||
|
deferTask, errorDetails, err := c.commonCreateTask(task, &ret)
|
||||||
|
defer deferTask()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// should fall back to FAILED in case it actually fails before we
|
||||||
|
// can set it to SUCCEEDED
|
||||||
|
task.Status = peridotpb.TaskStatus_TASK_STATUS_FAILED
|
||||||
|
|
||||||
|
stage2Ctx := workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
|
||||||
|
ScheduleToStartTimeout: 25 * time.Hour,
|
||||||
|
StartToCloseTimeout: 30 * time.Hour,
|
||||||
|
HeartbeatTimeout: 30 * time.Second,
|
||||||
|
TaskQueue: c.mainQueue,
|
||||||
|
// Yumrepofs is locking for a short period so let's not wait too long to retry
|
||||||
|
RetryPolicy: &temporal.RetryPolicy{
|
||||||
|
InitialInterval: 5 * time.Second,
|
||||||
|
BackoffCoefficient: 1.1,
|
||||||
|
MaximumInterval: 25 * time.Second,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
err = workflow.ExecuteActivity(stage2Ctx, c.SetUpdateInfoActivity, req).Get(stage2Ctx, &ret)
|
||||||
|
if err != nil {
|
||||||
|
setActivityError(errorDetails, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
task.Status = peridotpb.TaskStatus_TASK_STATUS_SUCCEEDED
|
||||||
|
|
||||||
|
return &ret, nil
|
||||||
|
}
|
24
peridot/cmd/v1/peridotadmin/BUILD.bazel
Normal file
24
peridot/cmd/v1/peridotadmin/BUILD.bazel
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "peridotadmin_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "peridot.resf.org/peridot/cmd/v1/peridotadmin",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//peridot/admin/v1:admin",
|
||||||
|
"//peridot/common",
|
||||||
|
"//peridot/db/connector",
|
||||||
|
"//temporalutils",
|
||||||
|
"//utils",
|
||||||
|
"//vendor/github.com/sirupsen/logrus",
|
||||||
|
"//vendor/github.com/spf13/cobra",
|
||||||
|
"//vendor/go.temporal.io/sdk/client",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "peridotadmin",
|
||||||
|
embed = [":peridotadmin_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
18
peridot/cmd/v1/peridotadmin/ci/BUILD.bazel
Normal file
18
peridot/cmd/v1/peridotadmin/ci/BUILD.bazel
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
load("//rules_resf:defs.bzl", "RESFDEPLOY_OUTS_MIGRATE", "container", "peridot_k8s")
|
||||||
|
|
||||||
|
container(
|
||||||
|
base = "//bases/bazel/go",
|
||||||
|
files = [
|
||||||
|
"//peridot/cmd/v1/peridotadmin",
|
||||||
|
],
|
||||||
|
image_name = "peridotadmin",
|
||||||
|
)
|
||||||
|
|
||||||
|
peridot_k8s(
|
||||||
|
name = "peridotadmin",
|
||||||
|
src = "deploy.jsonnet",
|
||||||
|
outs = RESFDEPLOY_OUTS_MIGRATE,
|
||||||
|
chart_yaml = "Chart.yaml",
|
||||||
|
values_yaml = "values.yaml",
|
||||||
|
deps = ["//ci"],
|
||||||
|
)
|
6
peridot/cmd/v1/peridotadmin/ci/Chart.yaml
Normal file
6
peridot/cmd/v1/peridotadmin/ci/Chart.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: peridotadmin
|
||||||
|
description: Helm chart for peridotadmin
|
||||||
|
type: application
|
||||||
|
version: 0.0.1
|
||||||
|
appVersion: "0.0.1"
|
75
peridot/cmd/v1/peridotadmin/ci/deploy.jsonnet
Normal file
75
peridot/cmd/v1/peridotadmin/ci/deploy.jsonnet
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
local resfdeploy = import 'ci/resfdeploy.jsonnet';
|
||||||
|
local db = import 'ci/db.jsonnet';
|
||||||
|
local kubernetes = import 'ci/kubernetes.jsonnet';
|
||||||
|
local temporal = import 'ci/temporal.jsonnet';
|
||||||
|
local utils = import 'ci/utils.jsonnet';
|
||||||
|
local s3 = import 'ci/s3.jsonnet';
|
||||||
|
|
||||||
|
resfdeploy.new({
|
||||||
|
name: 'peridotadmin',
|
||||||
|
helm_strip_prefix: 'PERIDOTADMIN_',
|
||||||
|
replicas: 1,
|
||||||
|
dbname: 'peridot',
|
||||||
|
backend: true,
|
||||||
|
migrate: true,
|
||||||
|
migrate_command: ['/bin/sh'],
|
||||||
|
migrate_args: ['-c', 'exit 0'],
|
||||||
|
legacyDb: true,
|
||||||
|
command: '/bundle/peridotadmin',
|
||||||
|
image: kubernetes.tag('peridotadmin'),
|
||||||
|
tag: kubernetes.version,
|
||||||
|
dsn: {
|
||||||
|
name: 'PERIDOTADMIN_DATABASE_URL',
|
||||||
|
value: db.dsn_legacy('peridot', false, 'peridotadmin'),
|
||||||
|
},
|
||||||
|
requests: if kubernetes.prod() then {
|
||||||
|
cpu: '0.2',
|
||||||
|
memory: '512M',
|
||||||
|
},
|
||||||
|
limits: if kubernetes.prod() then {
|
||||||
|
cpu: '2',
|
||||||
|
memory: '12G',
|
||||||
|
} else {
|
||||||
|
cpu: '2',
|
||||||
|
memory: '10G',
|
||||||
|
},
|
||||||
|
service_account_options: {
|
||||||
|
annotations: {
|
||||||
|
'eks.amazonaws.com/role-arn': if utils.helm_mode then '{{ .Values.awsRoleArn | default !"!" }}' else 'arn:aws:iam::893168113496:role/peridot_k8s_role',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ports: [
|
||||||
|
{
|
||||||
|
name: 'http',
|
||||||
|
containerPort: 15012,
|
||||||
|
protocol: 'TCP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'grpc',
|
||||||
|
containerPort: 15013,
|
||||||
|
protocol: 'TCP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
health: {
|
||||||
|
port: 15012,
|
||||||
|
},
|
||||||
|
env: [
|
||||||
|
{
|
||||||
|
name: 'PERIDOTADMIN_PRODUCTION',
|
||||||
|
value: if kubernetes.dev() then 'false' else 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'HYDRA_PUBLIC_HTTP_ENDPOINT_OVERRIDE',
|
||||||
|
value: if utils.helm_mode then '{{ .Values.hydraPublicEndpoint | default !"!" }}' else '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'HYDRA_ADMIN_HTTP_ENDPOINT_OVERRIDE',
|
||||||
|
value: if utils.helm_mode then '{{ .Values.hydraAdminEndpoint | default !"!" }}' else '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'SPICEDB_GRPC_ENDPOINT_OVERRIDE',
|
||||||
|
value: if utils.helm_mode then '{{ .Values.spicedbEndpoint | default !"!" }}' else '',
|
||||||
|
},
|
||||||
|
$.dsn,
|
||||||
|
] + s3.kube_env('PERIDOTADMIN') + temporal.kube_env('PERIDOTADMIN'),
|
||||||
|
})
|
3
peridot/cmd/v1/peridotadmin/ci/values.yaml
Normal file
3
peridot/cmd/v1/peridotadmin/ci/values.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Ports under requires ingressHost to be set during deploy
|
||||||
|
http:
|
||||||
|
ingressHost: null
|
81
peridot/cmd/v1/peridotadmin/main.go
Normal file
81
peridot/cmd/v1/peridotadmin/main.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||||
|
// Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||||
|
// Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
// may be used to endorse or promote products derived from this software without
|
||||||
|
// specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.temporal.io/sdk/client"
|
||||||
|
peridotadminv1 "peridot.resf.org/peridot/admin/v1"
|
||||||
|
peridotcommon "peridot.resf.org/peridot/common"
|
||||||
|
serverconnector "peridot.resf.org/peridot/db/connector"
|
||||||
|
"peridot.resf.org/temporalutils"
|
||||||
|
"peridot.resf.org/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var root = &cobra.Command{
|
||||||
|
Use: "peridotadmin",
|
||||||
|
Run: mn,
|
||||||
|
}
|
||||||
|
|
||||||
|
var cnf = utils.NewFlagConfig()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cnf.DefaultPort = 15012
|
||||||
|
|
||||||
|
dname := "peridot"
|
||||||
|
cnf.DatabaseName = &dname
|
||||||
|
cnf.Name = "peridotadmin"
|
||||||
|
|
||||||
|
peridotcommon.AddFlags(root.PersistentFlags())
|
||||||
|
utils.AddFlags(root.PersistentFlags(), cnf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mn(_ *cobra.Command, _ []string) {
|
||||||
|
c, err := temporalutils.NewClient(client.Options{})
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalln("unable to create Temporal client", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
s, err := peridotadminv1.NewServer(serverconnector.MustAuto(), c)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("could not init server: %v", err)
|
||||||
|
}
|
||||||
|
s.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
utils.Main()
|
||||||
|
if err := root.Execute(); err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
41
peridot/proto/v1/admin/BUILD.bazel
Normal file
41
peridot/proto/v1/admin/BUILD.bazel
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||||
|
|
||||||
|
proto_library(
|
||||||
|
name = "adminpb_proto",
|
||||||
|
srcs = ["admin.proto"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//apollo/proto/v1:apollopb_proto",
|
||||||
|
"//peridot/proto/v1:peridotpb_proto",
|
||||||
|
"@com_envoyproxy_protoc_gen_validate//validate:validate_proto",
|
||||||
|
"@go_googleapis//google/api:annotations_proto",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_proto_library(
|
||||||
|
name = "adminpb_go_proto",
|
||||||
|
compilers = [
|
||||||
|
"//:go_apiv2",
|
||||||
|
"//:go_grpc",
|
||||||
|
"//:go_validate",
|
||||||
|
"@com_github_grpc_ecosystem_grpc_gateway_v2//protoc-gen-grpc-gateway:go_gen_grpc_gateway",
|
||||||
|
],
|
||||||
|
importpath = "peridot.resf.org/peridot/admin/pb",
|
||||||
|
proto = ":adminpb_proto",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//apollo/proto/v1:pb",
|
||||||
|
"//peridot/proto/v1:pb",
|
||||||
|
"@com_envoyproxy_protoc_gen_validate//validate:validate_go_proto",
|
||||||
|
"@go_googleapis//google/api:annotations_go_proto",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "pb",
|
||||||
|
embed = [":adminpb_go_proto"],
|
||||||
|
importpath = "peridot.resf.org/peridot/admin/pb",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
30
peridot/proto/v1/admin/admin.proto
Normal file
30
peridot/proto/v1/admin/admin.proto
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package resf.peridot.admin.v1;
|
||||||
|
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
import "apollo/proto/v1/advisory.proto";
|
||||||
|
import "peridot/proto/v1/task.proto";
|
||||||
|
|
||||||
|
option go_package = "peridot.resf.org/peridot/admin/pb;adminpb";
|
||||||
|
|
||||||
|
service PeridotAdminService {
|
||||||
|
rpc AddUpdateInformation (AddUpdateInformationRequest) returns (resf.peridot.v1.AsyncTask) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v1/admin/add_update_information"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
option (resf.peridot.v1.task_info) = {
|
||||||
|
response_type: "AddUpdateInformationTask"
|
||||||
|
metadata_type: "AddUpdateInformationRequest"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddUpdateInformationRequest {
|
||||||
|
string project_id = 1 [(validate.rules).string.min_len = 1];
|
||||||
|
string product_name = 2 [(validate.rules).string.min_len = 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddUpdateInformationTask {}
|
Loading…
Reference in New Issue
Block a user