Skip to content

Commit

Permalink
feat: Updater rollback (#584)
Browse files Browse the repository at this point in the history
* start rollback

* more wip

* more tests

* add licenses, more testing

* split out action stuff to separate package, more testing

Needed to do this due to circular deps in mocks

* move service test data

* fix up darwin tests

* Fix linux service to fit new service interface

* fix windows service (todo: tests)

* add windows backup test

* fix service action pointin to wrong file

* Logic for Updater to monitor Collector Status (#581)

* Added default file name into package state to be accessed by updater

Signed-off-by: Corbin Phelps <[email protected]>

* Added logic to monitor status of collector from updater

Signed-off-by: Corbin Phelps <[email protected]>

* Added tests and fixuped some ci-checks issues

Signed-off-by: Corbin Phelps <[email protected]>

* Ran make add-license

Signed-off-by: Corbin Phelps <[email protected]>

* Added mocks for updater state monitor

Signed-off-by: Corbin Phelps <[email protected]>

* Pre-PR fixups

Signed-off-by: Corbin Phelps <[email protected]>

* Modified monitor state logic to be more flexible on errors

Signed-off-by: Corbin Phelps <[email protected]>

* fix revive linting errors

* fix windows gosec error

* update gosec to ignore test program

* refactor CopyFile to allow failure on overwrite

* refactor file action to take relative dir

* add interface enforcement to actions

* add nosec to open func

* split windows service backup function into a few functions

Co-authored-by: Corbin Phelps <[email protected]>
  • Loading branch information
BinaryFissionGames and Corbin Phelps committed Jul 29, 2022
1 parent 1ec7274 commit a277b1e
Show file tree
Hide file tree
Showing 47 changed files with 2,613 additions and 196 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ tidy:
gosec:
gosec -exclude-dir updater ./...
# exclude the testdata dir; it contains a go program for testing.
cd updater; gosec -exclude-dir internal/install/testdata ./...
cd updater; gosec -exclude-dir internal/service/testdata ./...

# This target performs all checks that CI will do (excluding the build itself)
.PHONY: ci-checks
Expand Down
2 changes: 1 addition & 1 deletion opamp/observiq/observiq_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func NewClient(args *NewClientArgs) (opamp.Client, error) {
downloadableFileManager: newDownloadableFileManager(clientLogger, args.TmpPath),
collector: args.Collector,
currentConfig: args.Config,
packagesStateProvider: newPackagesStateProvider(clientLogger, "package_statuses.json"),
packagesStateProvider: newPackagesStateProvider(clientLogger, packagestate.DefaultFileName),
}

// Parse URL to determin scheme
Expand Down
3 changes: 3 additions & 0 deletions packagestate/packages_state_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import (
// CollectorPackageName is the name for the top level packages for this collector
const CollectorPackageName = "observiq-otel-collector"

// DefaultFileName is the default name of the file use to store state
const DefaultFileName = "package_statuses.json"

// StateManager tracks Package states
type StateManager interface {
// LoadStatuses retrieves the previously saved PackagesStatuses.
Expand Down
58 changes: 56 additions & 2 deletions updater/cmd/updater/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,21 @@
package main

import (
"context"
"errors"
"fmt"
"log"
"os"
"time"

"github.com/observiq/observiq-otel-collector/packagestate"
"github.com/observiq/observiq-otel-collector/updater/internal/install"
"github.com/observiq/observiq-otel-collector/updater/internal/rollback"
"github.com/observiq/observiq-otel-collector/updater/internal/state"
"github.com/observiq/observiq-otel-collector/updater/internal/version"
"github.com/open-telemetry/opamp-go/protobufs"
"github.com/spf13/pflag"
"go.uber.org/zap"
)

// Unimplemented
Expand All @@ -43,12 +51,58 @@ func main() {
os.Exit(1)
}

// Create a monitor and load the package status file
// TODO replace nop logger with real one
monitor, err := state.NewCollectorMonitor(zap.NewNop())
if err != nil {
log.Fatalln("Failed to create monitor:", err)
}

installer, err := install.NewInstaller(*tmpDir)
if err != nil {
log.Fatalf("Failed to create installer: %s", err)
}

if err := installer.Install(); err != nil {
log.Fatalf("Failed to install: %s", err)
rb, err := rollback.NewRollbacker(*tmpDir)
if err != nil {
log.Fatalf("Failed to create rollbacker: %s", err)
}

if err := rb.Backup(); err != nil {
log.Fatalf("Failed to backup: %s", err)
}

if err := installer.Install(rb); err != nil {
log.Default().Printf("Failed to install: %s", err)

// Set the state to failed before rollback so collector knows it failed
if setErr := monitor.SetState(packagestate.DefaultFileName, protobufs.PackageStatus_InstallFailed, err); setErr != nil {
log.Println("Failed to set state on install failure:", setErr)
}
rb.Rollback()
log.Default().Fatalf("Rollback complete")
}

// Create a context with timeout to wait for a success or failed status
checkCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// Monitor the install state
if err := monitor.MonitorForSuccess(checkCtx, packagestate.DefaultFileName); err != nil {
log.Println("Failed to install:", err)

// If this is not an error due to the collector setting a failed status we need to set a failed status
if !errors.Is(err, state.ErrFailedStatus) {
// Set the state to failed before rollback so collector knows it failed
if setErr := monitor.SetState(packagestate.DefaultFileName, protobufs.PackageStatus_InstallFailed, err); setErr != nil {
log.Println("Failed to set state on install failure:", setErr)
}
}

rb.Rollback()
log.Fatalln("Rollback complete")
}

// Successful update
log.Println("Update Complete")
}
14 changes: 11 additions & 3 deletions updater/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ go 1.17

require (
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/observiq/observiq-otel-collector/packagestate v0.0.0-00010101000000-000000000000
github.com/open-telemetry/opamp-go v0.2.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.2
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211
github.com/stretchr/testify v1.8.0
go.uber.org/zap v1.21.0
golang.org/x/sys v0.0.0-20210510120138-977fb7262007
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.1.0 // indirect
github.com/stretchr/objx v0.4.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace github.com/observiq/observiq-otel-collector/packagestate => ../packagestate
74 changes: 68 additions & 6 deletions updater/go.sum
Original file line number Diff line number Diff line change
@@ -1,19 +1,81 @@
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/open-telemetry/opamp-go v0.2.0 h1:dV7wTkG5XNiorU62N1CJPr3f5dM0PGEtUUBtvK+LEG0=
github.com/open-telemetry/opamp-go v0.2.0/go.mod h1:IMdeuHGVc5CjKSu5/oNV0o+UmiXuahoHvoZ4GOmAI9M=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
21 changes: 21 additions & 0 deletions updater/internal/action/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright observIQ, Inc.
//
// 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 action

// RollbackableAction is an interface to represents an install action that may be rolled back.
//go:generate mockery --name RollbackableAction --filename rollbackable_action.go
type RollbackableAction interface {
Rollback() error
}
82 changes: 82 additions & 0 deletions updater/internal/action/file_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright observIQ, Inc.
//
// 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 action

import (
"errors"
"fmt"
"os"
"path/filepath"

"github.com/observiq/observiq-otel-collector/updater/internal/file"
"github.com/observiq/observiq-otel-collector/updater/internal/path"
)

// CopyFileAction is an action that records a file being copied from FromPath to ToPath
type CopyFileAction struct {
// FromPathRel is the path where the file originated, relative to the "latest"
// directory
FromPathRel string
// ToPath is the path where the file was written.
ToPath string
// FileCreated is a bool that records whether this action had to create a new file or not
FileCreated bool
backupDir string
latestDir string
}

var _ RollbackableAction = (*CopyFileAction)(nil)

// NewCopyFileAction creates a new CopyFileAction that indicates a file was copied from
// fromPathRel into toPath. tmpDir is specified for rollback purposes.
// NOTE: This action MUST be created BEFORE the action actually takes place; This allows
// for previous existence of the file to be recorded.
func NewCopyFileAction(fromPathRel, toPath, tmpDir string) (*CopyFileAction, error) {
fileExists := true
_, err := os.Stat(toPath)
switch {
case errors.Is(err, os.ErrNotExist):
fileExists = false
case err != nil:
return nil, fmt.Errorf("unexpected error stat-ing file: %w", err)
}

return &CopyFileAction{
FromPathRel: fromPathRel,
ToPath: toPath,
// The file will be created if it doesn't already exist
FileCreated: !fileExists,
backupDir: path.BackupDirFromTempDir(tmpDir),
latestDir: path.LatestDirFromTempDir(tmpDir),
}, nil
}

// Rollback will undo the file copy, by either deleting the file if the file did not originally exist,
// or it will copy the old file in the rollback dir if it already exists.
func (c CopyFileAction) Rollback() error {
if c.FileCreated {
// File did not exist before this action.
// We just need to delete this file.
return os.RemoveAll(c.ToPath)
}

// join the relative path to the backup directory to get the location of the backup path
backupFilePath := filepath.Join(c.backupDir, c.FromPathRel)
if err := file.CopyFile(backupFilePath, c.ToPath, true); err != nil {
return fmt.Errorf("failed to copy file: %w", err)
}

return nil
}
Loading

0 comments on commit a277b1e

Please sign in to comment.