Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Updater rollback #584

Merged
merged 21 commits into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d94ed64
start rollback
BinaryFissionGames Jul 19, 2022
479c86a
more wip
BinaryFissionGames Jul 19, 2022
f1f9c3d
more tests
BinaryFissionGames Jul 20, 2022
6ed79eb
add licenses, more testing
BinaryFissionGames Jul 20, 2022
2c4e843
split out action stuff to separate package, more testing
BinaryFissionGames Jul 20, 2022
4fd2fc2
move service test data
BinaryFissionGames Jul 20, 2022
0f9d62b
fix up darwin tests
BinaryFissionGames Jul 20, 2022
f9195ea
Fix linux service to fit new service interface
BinaryFissionGames Jul 20, 2022
f29fe68
fix windows service (todo: tests)
BinaryFissionGames Jul 21, 2022
557b34d
add windows backup test
BinaryFissionGames Jul 24, 2022
f7a97ee
fix service action pointin to wrong file
BinaryFissionGames Jul 25, 2022
2dc88ad
Logic for Updater to monitor Collector Status (#581)
Jul 25, 2022
1e67cbc
fix revive linting errors
BinaryFissionGames Jul 25, 2022
8531b26
fix windows gosec error
BinaryFissionGames Jul 25, 2022
c61aaa4
Merge remote-tracking branch 'origin/updater-rollback' into updater-r…
BinaryFissionGames Jul 25, 2022
dfb1dda
update gosec to ignore test program
BinaryFissionGames Jul 25, 2022
997d973
refactor CopyFile to allow failure on overwrite
BinaryFissionGames Jul 25, 2022
aa5c1fe
refactor file action to take relative dir
BinaryFissionGames Jul 25, 2022
245b6ae
add interface enforcement to actions
BinaryFissionGames Jul 25, 2022
9065f6f
add nosec to open func
BinaryFissionGames Jul 25, 2022
c321826
split windows service backup function into a few functions
BinaryFissionGames Jul 26, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is mockery a tool we have on our install-tools list? If not we should add it and add a make task to generate these.

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