diff --git a/go.mod b/go.mod index 73ab8c1f..31f100bc 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,7 @@ module github.com/bottlerocket-os/bottlerocket-update-operator go 1.12 require ( - github.com/coreos/go-systemd/v22 v22.0.0 - github.com/godbus/dbus/v5 v5.0.3 + github.com/Masterminds/semver v1.5.0 github.com/google/go-cmp v0.3.1 // indirect github.com/googleapis/gnostic v0.3.1 // indirect github.com/imdario/mergo v0.3.7 // indirect @@ -12,6 +11,7 @@ require ( github.com/karlseguin/expect v1.0.1 // indirect github.com/pkg/errors v0.8.1 github.com/sirupsen/logrus v1.4.2 + github.com/stretchr/testify v1.3.0 github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 // indirect golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 // indirect golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect diff --git a/go.sum b/go.sum index 73509ad3..3f41ba7c 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -25,8 +27,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28= -github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -58,8 +58,6 @@ github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nA github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= diff --git a/main.go b/main.go index b31ced7b..fd6391ab 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,6 @@ import ( "github.com/bottlerocket-os/bottlerocket-update-operator/pkg/controller" "github.com/bottlerocket-os/bottlerocket-update-operator/pkg/k8sutil" "github.com/bottlerocket-os/bottlerocket-update-operator/pkg/logging" - "github.com/bottlerocket-os/bottlerocket-update-operator/pkg/platform/updog" "github.com/bottlerocket-os/bottlerocket-update-operator/pkg/sigcontext" "github.com/pkg/errors" "k8s.io/client-go/kubernetes" @@ -89,11 +88,7 @@ func runController(ctx context.Context, kube kubernetes.Interface, nodeName stri func runAgent(ctx context.Context, kube kubernetes.Interface, nodeName string) error { log := logging.New("agent") - platform, err := updog.New() - if err != nil { - return errors.WithMessage(err, "could not setup platform for agent") - } - a, err := agent.New(log, kube, platform, nodeName) + a, err := agent.New(log, kube, nil, nodeName) if err != nil { return err } diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 52784cae..a21dbb33 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -2,6 +2,8 @@ package agent import ( "context" + update_api "github.com/bottlerocket-os/bottlerocket-update-operator/pkg/platform/update-api" + "github.com/bottlerocket-os/bottlerocket-update-operator/pkg/platform/updog" "os" "time" @@ -67,18 +69,42 @@ type proc interface { KillProcess() error } -func New(log logging.Logger, kube kubernetes.Interface, plat platform.Platform, nodeName string) (*Agent, error) { +func New(log logging.Logger, kube kubernetes.Interface, platform platform.Platform, nodeName string) (*Agent, error) { if nodeName == "" { return nil, errors.New("nodeName must be provided for Agent to manage") } var nodeclient corev1.NodeInterface if kube != nil { nodeclient = kube.CoreV1().Nodes() + // Determine which platform to use depending on the updater interface version + var node, err = nodeclient.Get(nodeName, v1meta.GetOptions{}) + if err != nil { + return nil, errors.New("failed to retrieve node information") + } + // Get the updater interface version from the node label + var platformVersion = node.Labels[marker.UpdaterInterfaceVersionKey] + switch platformVersion { + case "2.0.0": + platform, err = update_api.New() + if err != nil { + return nil, errors.WithMessage(err, "could not setup Update API platform for agent") + } + // If the updater interface version is not specified, default back to using Updog as the platform + default: + log.Info("unknown platform version specified, defaulting to using updog") + fallthrough + case "1.0.0": + platform, err = updog.New() + if err != nil { + return nil, errors.WithMessage(err, "could not setup Updog platform for agent") + } + } } + return &Agent{ log: log, kube: kube, - platform: plat, + platform: platform, poster: &k8sPoster{log, nodeclient}, proc: &osProc{}, nodeName: nodeName, @@ -314,7 +340,7 @@ func (a *Agent) realize(in *intent.Intent) error { case marker.NodeActionUnknown, marker.NodeActionStabilize: log.Debug("sitrep") - _, err = a.platform.Status() + err = platform.Ping(a.platform) if err != nil { break } diff --git a/pkg/platform/update-api/platform.go b/pkg/platform/update-api/platform.go new file mode 100644 index 00000000..e6116d86 --- /dev/null +++ b/pkg/platform/update-api/platform.go @@ -0,0 +1,163 @@ +package update_api + +import ( + "context" + "encoding/json" + "github.com/Masterminds/semver" + "github.com/bottlerocket-os/bottlerocket-update-operator/pkg/logging" + "github.com/bottlerocket-os/bottlerocket-update-operator/pkg/platform" + "github.com/pkg/errors" + "io/ioutil" + "net" + "net/http" + "time" +) + +// Assert Update-API as a platform implementor. +var _ platform.Platform = (*Platform)(nil) + +type Platform struct { + log logging.Logger + httpClient http.Client +} + +func New() (*Platform, error) { + return &Platform{log: logging.New("platform"), httpClient: http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", bottlerocketApiSock) + }, + }, + // Set a 10 second timeout for all requests + Timeout: 10 * time.Second, + }}, nil +} + +type statusResponse struct { + osVersion *semver.Version +} + +func (sr *statusResponse) OK() bool { + // Bottlerocket OS version needs to be at least 0.4.1 to support the Update API + constraint, _ := semver.NewConstraint(">= 0.4.1") + return constraint.Check(sr.osVersion) +} + +// Try to determine if the Bottlerocket version at least 0.4.1 +func (p Platform) Status() (platform.Status, error) { + response, err := p.httpClient.Get("http://unix" + "/os") + if err != nil { + p.log.WithError(err).Error("error when trying to GET '/os'") + return nil, err + } + var osInfo interface{} + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + err = json.Unmarshal(body, osInfo) + if err != nil { + return nil, err + } + // Assert 'version_id' in the OS info json output as string + osVersion, err := semver.NewVersion(osInfo.(map[string]interface{})["version_id"].(string)) + if err != nil { + return nil, errors.Wrap(err, "failed to parse 'version_id' field as semver") + } + return &statusResponse{osVersion: osVersion}, nil +} + +type listAvailableResponse struct { + chosenUpdate *UpdateImage +} + +func (lar *listAvailableResponse) Updates() []platform.Update { + if lar.chosenUpdate == nil { + return nil + } + updates := make([]platform.Update, 1) + updates[0] = lar.chosenUpdate + return updates +} + +func (p Platform) ListAvailable() (platform.Available, error) { + p.log.Debug("fetching list of available updates") + + // Refresh list of updates and check if there are any available + err := postAction(p.httpClient, "/actions/refresh-updates") + if err != nil { + return nil, err + } + + updateStatus, err := getUpdateStatus(p.httpClient) + if updateStatus.MostRecentCommand.CmdType == refresh && updateStatus.MostRecentCommand.CmdStatus == Success { + // If there is no available update to update to + if updateStatus.ChosenUpdate == nil { + return &listAvailableResponse{chosenUpdate: nil}, err + } + return &listAvailableResponse{chosenUpdate: updateStatus.ChosenUpdate}, err + } else { + return &listAvailableResponse{chosenUpdate: nil}, errors.New("failed to refresh updates or update action performed out of band") + } +} + +func (p Platform) Prepare(target platform.Update) error { + updateStatus, err := getUpdateStatus(p.httpClient) + if err != nil { + return err + } + if updateStatus.UpdateState != Available && updateStatus.UpdateState != Staged { + return errors.Errorf("unexpected update state: %s, expecting state to be 'Available' or 'Staged'. update action performed out of band?", updateStatus.UpdateState) + } + + // Download the update and apply it to the inactive partition + err = postAction(p.httpClient, "/actions/prepare-update") + if err != nil { + return err + } + + updateStatus, err = getUpdateStatus(p.httpClient) + if updateStatus.MostRecentCommand.CmdType != prepare || updateStatus.MostRecentCommand.CmdStatus != Success { + return errors.New("failed to prepare update or update action performed out of band") + } + return nil +} + +func (p Platform) Update(target platform.Update) error { + updateStatus, err := getUpdateStatus(p.httpClient) + if err != nil { + return err + } + if updateStatus.UpdateState != Staged { + return errors.Errorf("unexpected update state: %s, expecting state to be 'Staged'. update action performed out of band?", updateStatus.UpdateState) + } + + // Activate the prepared update + err = postAction(p.httpClient, "/actions/activate-update") + if err != nil { + return err + } + + updateStatus, err = getUpdateStatus(p.httpClient) + if updateStatus.MostRecentCommand.CmdType != activate || updateStatus.MostRecentCommand.CmdStatus != Success { + return errors.New("failed to activate update or update action performed out of band") + } + return nil +} + +func (p Platform) BootUpdate(target platform.Update, rebootNow bool) error { + updateStatus, err := getUpdateStatus(p.httpClient) + if err != nil { + return err + } + if updateStatus.UpdateState != Ready { + return errors.Errorf("unexpected update state: %s, expecting state to be 'Ready'. update action performed out of band?", updateStatus.UpdateState) + } + + // Reboot the host into the activated update + err = postAction(p.httpClient, "/actions/reboot") + if err != nil { + return err + } + return nil +} diff --git a/pkg/platform/update-api/update_api.go b/pkg/platform/update-api/update_api.go new file mode 100644 index 00000000..874b6b44 --- /dev/null +++ b/pkg/platform/update-api/update_api.go @@ -0,0 +1,134 @@ +package update_api + +import ( + "encoding/json" + "github.com/pkg/errors" + "io/ioutil" + "net/http" + "time" +) + +const bottlerocketApiSock = "/run/api.sock" + +type updateState = string + +const ( + Idle updateState = "Idle" + Available updateState = "Available" + Staged updateState = "Staged" + Ready updateState = "Ready" +) + +type UpdateImage struct { + Arch string `json:"arch"` + Version string `json:"version"` + Variant string `json:"variant"` +} + +func (ui *UpdateImage) Identifier() interface{} { + return ui.Version +} + +type StagedImage struct { + Image UpdateImage `json:"image"` + NextToBoot bool `json:"next_to_boot"` +} + +type updateCommand = string + +const ( + refresh updateCommand = "refresh" + prepare updateCommand = "prepare" + activate updateCommand = "activate" + deactivate updateCommand = "deactivate" +) + +type commandStatus = string + +const ( + Success commandStatus = "Success" + Failed commandStatus = "Failed" + Unknown commandStatus = "Unknown" +) + +type CommandResult struct { + CmdType updateCommand `json:"cmd_type"` + CmdStatus commandStatus `json:"cmd_status"` + Timestamp string `json:"timestamp"` + ExitStatus *int32 `json:"exit_status"` + Stderr *string `json:"stderr"` +} + +type UpdateStatus struct { + UpdateState updateState `json:"update_state"` + AvailableUpdates []string `json:"available_updates"` + ChosenUpdate *UpdateImage `json:"chosen_update"` + ActivePartition *StagedImage `json:"active_partition"` + StagingPartition *StagedImage `json:"staging_partition"` + MostRecentCommand *CommandResult `json:"most_recent_command"` +} + +// getUpdateStatus repeatedly tries to GET /updates/status until a status is returned +// We only retry the request if the response was a 423 Locked (indicates that the update status is not ready) +func getUpdateStatus(httpClient http.Client) (*UpdateStatus, error) { + var response *http.Response + var err error + var attempts = 0 + // Retry up to 5 times in case the Update API is busy; Waiting 10 seconds between each attempt. + for attempts < 5 { + response, err = httpClient.Get("http://unix" + "/updates/status") + if err != nil { + errors.New("error when trying to GET '/updates/status'") + return nil, err + } + // Response OK, break out of loop + if response.StatusCode == 204 { + break + } else if response.StatusCode == 423 { + // Retry if we get a 423 Locked response (update API busy) + time.Sleep(10 * time.Second) + } else { + return nil, errors.Errorf("bad http response when trying to GET '/actions/refresh-updates': status code %d", response.StatusCode) + } + } + if attempts == 5 { + return nil, errors.New("ran out of tries waiting for the Update API to be available") + } + + var updateStatus *UpdateStatus + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + err = json.Unmarshal(body, updateStatus) + if err != nil { + return nil, err + } + return updateStatus, nil +} + +func postAction(httpClient http.Client, path string) error { + var response *http.Response + var err error + var attempts = 0 + // Retry up to 5 times in case the Update API is busy; Waiting 10 seconds between each attempt. + for attempts < 5 { + response, err = httpClient.Post("http://unix"+path, "text/plain", http.NoBody) + if err != nil { + return errors.Wrapf(err, "error when POSTing to '%s'", path) + } + // Response OK, break out of loop + if response.StatusCode == 204 { + break + } else if response.StatusCode == 423 { + // Retry after ten seconds if we get a 423 Locked response (update API busy) + time.Sleep(10 * time.Second) + } else { + return errors.Errorf("bad http response when POSTing to '%s': status code: %d, status: %s", path, response.StatusCode, response.Status) + } + } + if attempts == 5 { + return errors.New("ran out of tries waiting for the Update API to be available") + } + return nil +} diff --git a/pkg/platform/update-api/update_api_test.go b/pkg/platform/update-api/update_api_test.go new file mode 100644 index 00000000..d8678d48 --- /dev/null +++ b/pkg/platform/update-api/update_api_test.go @@ -0,0 +1,149 @@ +package update_api + +import ( + "encoding/json" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestUnmarshallUpdateStatus(t *testing.T) { + update_string := "Starting update to 0.4.0\n" + cases := []struct { + Name string + UpdateStatusJson []byte + Expected UpdateStatus + }{ + { + Name: "No update available after refresh", + UpdateStatusJson: []byte(`{"update_state":"Idle","available_updates":["0.4.0","0.3.4","0.3.3","0.3.2","0.3.1","0.3.0"],"chosen_update":null,"active_partition":{"image":{"arch":"x86_64","version":"0.4.0","variant":"aws-k8s-1.15"},"next_to_boot":true},"staging_partition":null,"most_recent_command":{"cmd_type":"refresh","cmd_status":"Success","timestamp":"2020-07-08T21:32:35.802253160Z","exit_status":0,"stderr":""}}`), + Expected: UpdateStatus{ + UpdateState: Idle, + AvailableUpdates: []string{"0.4.0", "0.3.4", "0.3.3", "0.3.2", "0.3.1", "0.3.0"}, + ChosenUpdate: nil, + ActivePartition: &StagedImage{ + Image: UpdateImage{ + Arch: "x86_64", + Version: "0.4.0", + Variant: "aws-k8s-1.15", + }, + NextToBoot: true, + }, + StagingPartition: nil, + MostRecentCommand: &CommandResult{ + CmdType: refresh, + CmdStatus: Success, + Timestamp: "2020-07-08T21:32:35.802253160Z", + ExitStatus: new(int32), + Stderr: new(string), + }, + }, + }, + { + Name: "Update available after refresh", + UpdateStatusJson: []byte(`{"update_state":"Available","available_updates":["0.4.0","0.3.4","0.3.3","0.3.2","0.3.1","0.3.0"],"chosen_update":{"arch":"x86_64","version":"0.4.0","variant":"aws-k8s-1.15"},"active_partition":{"image":{"arch":"x86_64","version":"0.3.2","variant":"aws-k8s-1.15"},"next_to_boot":true},"staging_partition":null,"most_recent_command":{"cmd_type":"refresh","cmd_status":"Success","timestamp":"2020-06-18T17:57:43.141433622Z","exit_status":0,"stderr":""}}`), + Expected: UpdateStatus{ + UpdateState: Available, + AvailableUpdates: []string{"0.4.0", "0.3.4", "0.3.3", "0.3.2", "0.3.1", "0.3.0"}, + ChosenUpdate: &UpdateImage{ + Arch: "x86_64", + Version: "0.4.0", + Variant: "aws-k8s-1.15", + }, + ActivePartition: &StagedImage{ + Image: UpdateImage{ + Arch: "x86_64", + Version: "0.3.2", + Variant: "aws-k8s-1.15", + }, + NextToBoot: true, + }, + StagingPartition: nil, + MostRecentCommand: &CommandResult{ + CmdType: refresh, + CmdStatus: Success, + Timestamp: "2020-06-18T17:57:43.141433622Z", + ExitStatus: new(int32), + Stderr: new(string), + }, + }, + }, + { + Name: "Update staged", + UpdateStatusJson: []byte(`{"update_state":"Staged","available_updates":["0.4.0","0.3.4","0.3.3","0.3.2","0.3.1","0.3.0"],"chosen_update":{"arch":"x86_64","version":"0.4.0","variant":"aws-k8s-1.15"},"active_partition":{"image":{"arch":"x86_64","version":"0.3.4","variant":"aws-k8s-1.15"},"next_to_boot":true},"staging_partition":{"image":{"arch":"x86_64","version":"0.4.0","variant":"aws-k8s-1.15"},"next_to_boot":false},"most_recent_command":{"cmd_type":"prepare","cmd_status":"Success","timestamp":"2020-07-10T06:44:58.766493367Z","exit_status":0,"stderr":"Starting update to 0.4.0\n"}}`), + Expected: UpdateStatus{ + UpdateState: Staged, + AvailableUpdates: []string{"0.4.0", "0.3.4", "0.3.3", "0.3.2", "0.3.1", "0.3.0"}, + ChosenUpdate: &UpdateImage{ + Arch: "x86_64", + Version: "0.4.0", + Variant: "aws-k8s-1.15", + }, + ActivePartition: &StagedImage{ + Image: UpdateImage{ + Arch: "x86_64", + Version: "0.3.4", + Variant: "aws-k8s-1.15", + }, + NextToBoot: true, + }, + StagingPartition: &StagedImage{ + Image: UpdateImage{ + Arch: "x86_64", + Version: "0.4.0", + Variant: "aws-k8s-1.15", + }, + NextToBoot: false, + }, + MostRecentCommand: &CommandResult{ + CmdType: prepare, + CmdStatus: Success, + Timestamp: "2020-07-10T06:44:58.766493367Z", + ExitStatus: new(int32), + Stderr: &update_string, + }, + }, + }, + { + Name: "Update ready", + UpdateStatusJson: []byte(`{"update_state":"Ready","available_updates":["0.4.0","0.3.4","0.3.3","0.3.2","0.3.1","0.3.0"],"chosen_update":{"arch":"x86_64","version":"0.4.0","variant":"aws-k8s-1.15"},"active_partition":{"image":{"arch":"x86_64","version":"0.3.4","variant":"aws-k8s-1.15"},"next_to_boot":false},"staging_partition":{"image":{"arch":"x86_64","version":"0.4.0","variant":"aws-k8s-1.15"},"next_to_boot":true},"most_recent_command":{"cmd_type":"activate","cmd_status":"Success","timestamp":"2020-07-10T06:47:19.903337270Z","exit_status":0,"stderr":""}}`), + Expected: UpdateStatus{ + UpdateState: Ready, + AvailableUpdates: []string{"0.4.0", "0.3.4", "0.3.3", "0.3.2", "0.3.1", "0.3.0"}, + ChosenUpdate: &UpdateImage{ + Arch: "x86_64", + Version: "0.4.0", + Variant: "aws-k8s-1.15", + }, + ActivePartition: &StagedImage{ + Image: UpdateImage{ + Arch: "x86_64", + Version: "0.3.4", + Variant: "aws-k8s-1.15", + }, + NextToBoot: false, + }, + StagingPartition: &StagedImage{ + Image: UpdateImage{ + Arch: "x86_64", + Version: "0.4.0", + Variant: "aws-k8s-1.15", + }, + NextToBoot: true, + }, + MostRecentCommand: &CommandResult{ + CmdType: activate, + CmdStatus: Success, + Timestamp: "2020-07-10T06:47:19.903337270Z", + ExitStatus: new(int32), + Stderr: new(string), + }, + }, + }, + } + for _, tc := range cases { + var unmarshaledStatus UpdateStatus + err := json.Unmarshal(tc.UpdateStatusJson, &unmarshaledStatus) + assert.NoError(t, err, "failed to unmarshal into update status") + assert.Equal(t, tc.Expected, unmarshaledStatus) + } +} diff --git a/update-operator.yaml b/update-operator.yaml index 15c370d1..30cf7166 100644 --- a/update-operator.yaml +++ b/update-operator.yaml @@ -179,9 +179,6 @@ spec: valueFrom: fieldRef: fieldPath: spec.nodeName - securityContext: - # Required for executing OS update operations. - privileged: true resources: limits: memory: 600Mi @@ -189,10 +186,10 @@ spec: cpu: 100m memory: 600Mi volumeMounts: - - name: rootfs - mountPath: /.bottlerocket/rootfs + - name: bottlerocket-api-socket + mountPath: /run/api.sock volumes: - - name: rootfs + - name: bottlerocket-api-socket hostPath: - path: / - type: Directory + path: /run/api.sock + type: Socket