blob: c9d2b5de7232ca577bb98363ee32b467ba9c3430 [file] [log] [blame]
// Copyright 2015 The LUCI 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 main
import (
"context"
"errors"
"fmt"
"log"
"os"
"os/signal"
"path/filepath"
"time"
"github.com/golang/protobuf/proto"
"github.com/maruel/subcommands"
"github.com/luci/luci-go/client/archiver"
"github.com/luci/luci-go/client/isolate"
"github.com/luci/luci-go/common/auth"
"github.com/luci/luci-go/common/data/text/units"
logpb "github.com/luci/luci-go/common/eventlog/proto"
"github.com/luci/luci-go/common/isolatedclient"
)
func cmdArchive(defaultAuthOpts auth.Options) *subcommands.Command {
return &subcommands.Command{
UsageLine: "archive <options>",
ShortDesc: "creates a .isolated file and uploads the tree to an isolate server.",
LongDesc: "All the files listed in the .isolated file are put in the isolate server cache",
CommandRun: func() subcommands.CommandRun {
c := archiveRun{}
c.commonServerFlags.Init(defaultAuthOpts)
c.isolateFlags.Init(&c.Flags)
c.loggingFlags.Init(&c.Flags)
return &c
},
}
}
type archiveRun struct {
commonServerFlags
isolateFlags
loggingFlags loggingFlags
}
func (c *archiveRun) Parse(a subcommands.Application, args []string) error {
if err := c.commonServerFlags.Parse(); err != nil {
return err
}
cwd, err := os.Getwd()
if err != nil {
return err
}
if err := c.isolateFlags.Parse(cwd, RequireIsolatedFile); err != nil {
return err
}
if len(args) != 0 {
return errors.New("position arguments not expected")
}
return nil
}
func (c *archiveRun) main(a subcommands.Application, args []string) error {
start := time.Now()
authCl, err := c.createAuthClient()
if err != nil {
return err
}
ctx := c.defaultFlags.MakeLoggingContext(os.Stderr)
client := isolatedclient.New(nil, authCl, c.isolatedFlags.ServerURL, c.isolatedFlags.Namespace, nil, nil)
eventlogger := NewLogger(ctx, c.loggingFlags.EventlogEndpoint)
archiveDetails, err := doArchive(ctx, client, &c.ArchiveOptions, c.defaultFlags.Quiet, start)
if err != nil {
return err
}
end := time.Now()
op := logpb.IsolateClientEvent_LEGACY_ARCHIVE.Enum()
if err := eventlogger.logStats(ctx, op, start, end, archiveDetails); err != nil {
log.Printf("Failed to log to eventlog: %v", err)
}
return nil
}
// doArchive performs the archive operation for an isolate specified by archiveOpts.
func doArchive(ctx context.Context, client *isolatedclient.Client, archiveOpts *isolate.ArchiveOptions, quiet bool, start time.Time) (*logpb.IsolateClientEvent_ArchiveDetails, error) {
prefix := "\n"
if quiet {
prefix = ""
}
arch := archiver.New(ctx, client, os.Stdout)
CancelOnCtrlC(arch)
item := isolate.Archive(arch, archiveOpts)
item.WaitForHashed()
var err error
if err = item.Error(); err != nil {
fmt.Printf("%s%s %s\n", prefix, filepath.Base(archiveOpts.Isolate), err)
} else {
fmt.Printf("%s%s %s\n", prefix, item.Digest(), filepath.Base(archiveOpts.Isolate))
}
if err2 := arch.Close(); err == nil {
err = err2
}
stats := arch.Stats()
if !quiet {
duration := time.Since(start)
fmt.Fprintf(os.Stderr, "Hits : %5d (%s)\n", stats.TotalHits(), stats.TotalBytesHits())
fmt.Fprintf(os.Stderr, "Misses : %5d (%s)\n", stats.TotalMisses(), stats.TotalBytesPushed())
fmt.Fprintf(os.Stderr, "Duration: %s\n", units.Round(duration, time.Millisecond))
}
archiveDetails := &logpb.IsolateClientEvent_ArchiveDetails{
HitCount: proto.Int64(int64(stats.TotalHits())),
MissCount: proto.Int64(int64(stats.TotalMisses())),
HitBytes: proto.Int64(int64(stats.TotalBytesHits())),
MissBytes: proto.Int64(int64(stats.TotalBytesPushed())),
}
if item.Error() != nil {
archiveDetails.IsolateHash = []string{string(item.Digest())}
}
return archiveDetails, nil
}
func (c *archiveRun) Run(a subcommands.Application, args []string, _ subcommands.Env) int {
if err := c.Parse(a, args); err != nil {
fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
return 1
}
cl, err := c.defaultFlags.StartTracing()
if err != nil {
fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
return 1
}
defer cl.Close()
if err := c.main(a, args); err != nil {
fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
return 1
}
return 0
}
// CancelOnCtrlC is a temporary copy of the CancelOnCtrlC in internal/common/concurrent.go
// This is needed until the old archive and batcharchive code (which uses Cancelers) is removed.
// It operates on a concrete Archiver to avoid the dependency on Canceler.
func CancelOnCtrlC(arch *archiver.Archiver) {
interrupted := make(chan os.Signal, 1)
signal.Notify(interrupted, os.Interrupt)
go func() {
defer signal.Stop(interrupted)
select {
case <-interrupted:
arch.Cancel(errors.New("Ctrl-C"))
case <-arch.Channel():
}
}()
}