github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/update.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"bufio"
    22  	"crypto"
    23  	"crypto/tls"
    24  	"encoding/hex"
    25  	"errors"
    26  	"fmt"
    27  	"io"
    28  	"net/http"
    29  	"net/url"
    30  	"os"
    31  	"path"
    32  	"path/filepath"
    33  	"runtime"
    34  	"strings"
    35  	"sync/atomic"
    36  	"time"
    37  
    38  	"github.com/klauspost/compress/zstd"
    39  	xhttp "github.com/minio/minio/internal/http"
    40  	"github.com/minio/minio/internal/logger"
    41  	"github.com/minio/pkg/v2/env"
    42  	xnet "github.com/minio/pkg/v2/net"
    43  	"github.com/minio/selfupdate"
    44  	gopsutilcpu "github.com/shirou/gopsutil/v3/cpu"
    45  	"github.com/valyala/bytebufferpool"
    46  )
    47  
    48  const (
    49  	envMinisignPubKey = "MINIO_UPDATE_MINISIGN_PUBKEY"
    50  	updateTimeout     = 10 * time.Second
    51  )
    52  
    53  // For windows our files have .exe additionally.
    54  var minioReleaseWindowsInfoURL = MinioReleaseURL + "minio.exe.sha256sum"
    55  
    56  // minioVersionToReleaseTime - parses a standard official release
    57  // MinIO version string.
    58  //
    59  // An official binary's version string is the release time formatted
    60  // with RFC3339 (in UTC) - e.g. `2017-09-29T19:16:56Z`
    61  func minioVersionToReleaseTime(version string) (releaseTime time.Time, err error) {
    62  	return time.Parse(time.RFC3339, version)
    63  }
    64  
    65  // releaseTimeToReleaseTag - converts a time to a string formatted as
    66  // an official MinIO release tag.
    67  //
    68  // An official minio release tag looks like:
    69  // `RELEASE.2017-09-29T19-16-56Z`
    70  func releaseTimeToReleaseTag(releaseTime time.Time) string {
    71  	return "RELEASE." + releaseTime.Format(MinioReleaseTagTimeLayout)
    72  }
    73  
    74  // releaseTagToReleaseTime - reverse of `releaseTimeToReleaseTag()`
    75  func releaseTagToReleaseTime(releaseTag string) (releaseTime time.Time, err error) {
    76  	fields := strings.Split(releaseTag, ".")
    77  	if len(fields) < 2 || len(fields) > 4 {
    78  		return releaseTime, fmt.Errorf("%s is not a valid release tag", releaseTag)
    79  	}
    80  	if fields[0] != "RELEASE" {
    81  		return releaseTime, fmt.Errorf("%s is not a valid release tag", releaseTag)
    82  	}
    83  	return time.Parse(MinioReleaseTagTimeLayout, fields[1])
    84  }
    85  
    86  // getModTime - get the file modification time of `path`
    87  func getModTime(path string) (t time.Time, err error) {
    88  	// Convert to absolute path
    89  	absPath, err := filepath.Abs(path)
    90  	if err != nil {
    91  		return t, fmt.Errorf("Unable to get absolute path of %s. %w", path, err)
    92  	}
    93  
    94  	// Version is minio non-standard, we will use minio binary's
    95  	// ModTime as release time.
    96  	fi, err := Stat(absPath)
    97  	if err != nil {
    98  		return t, fmt.Errorf("Unable to get ModTime of %s. %w", absPath, err)
    99  	}
   100  
   101  	// Return the ModTime
   102  	return fi.ModTime().UTC(), nil
   103  }
   104  
   105  // GetCurrentReleaseTime - returns this process's release time.  If it
   106  // is official minio version, parsed version is returned else minio
   107  // binary's mod time is returned.
   108  func GetCurrentReleaseTime() (releaseTime time.Time, err error) {
   109  	if releaseTime, err = minioVersionToReleaseTime(Version); err == nil {
   110  		return releaseTime, err
   111  	}
   112  
   113  	// Looks like version is minio non-standard, we use minio
   114  	// binary's ModTime as release time:
   115  	return getModTime(os.Args[0])
   116  }
   117  
   118  // IsDocker - returns if the environment minio is running in docker or
   119  // not. The check is a simple file existence check.
   120  //
   121  // https://github.com/moby/moby/blob/master/daemon/initlayer/setup_unix.go
   122  // https://github.com/containers/podman/blob/master/libpod/runtime.go
   123  //
   124  //	"/.dockerenv":        "file",
   125  //	"/run/.containerenv": "file",
   126  func IsDocker() bool {
   127  	var err error
   128  	for _, envfile := range []string{
   129  		"/.dockerenv",
   130  		"/run/.containerenv",
   131  	} {
   132  		_, err = os.Stat(envfile)
   133  		if err == nil {
   134  			return true
   135  		}
   136  	}
   137  	if osIsNotExist(err) {
   138  		// if none of the files are present we may be running inside
   139  		// CRI-O, Containerd etc..
   140  		// Fallback to our container specific ENVs if they are set.
   141  		return env.IsSet("MINIO_ACCESS_KEY_FILE")
   142  	}
   143  
   144  	// Log error, as we will not propagate it to caller
   145  	logger.LogIf(GlobalContext, err)
   146  
   147  	return err == nil
   148  }
   149  
   150  // IsDCOS returns true if minio is running in DCOS.
   151  func IsDCOS() bool {
   152  	// http://mesos.apache.org/documentation/latest/docker-containerizer/
   153  	// Mesos docker containerizer sets this value
   154  	return env.Get("MESOS_CONTAINER_NAME", "") != ""
   155  }
   156  
   157  // IsKubernetes returns true if minio is running in kubernetes.
   158  func IsKubernetes() bool {
   159  	// Kubernetes env used to validate if we are
   160  	// indeed running inside a kubernetes pod
   161  	// is KUBERNETES_SERVICE_HOST
   162  	// https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kubelet_pods.go#L541
   163  	return env.Get("KUBERNETES_SERVICE_HOST", "") != ""
   164  }
   165  
   166  // IsBOSH returns true if minio is deployed from a bosh package
   167  func IsBOSH() bool {
   168  	// "/var/vcap/bosh" exists in BOSH deployed instance.
   169  	_, err := os.Stat("/var/vcap/bosh")
   170  	if osIsNotExist(err) {
   171  		return false
   172  	}
   173  
   174  	// Log error, as we will not propagate it to caller
   175  	logger.LogIf(GlobalContext, err)
   176  
   177  	return err == nil
   178  }
   179  
   180  // MinIO Helm chart uses DownwardAPIFile to write pod label info to /podinfo/labels
   181  // More info: https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/#store-pod-fields
   182  // Check if this is Helm package installation and report helm chart version
   183  func getHelmVersion(helmInfoFilePath string) string {
   184  	// Read the file exists.
   185  	helmInfoFile, err := Open(helmInfoFilePath)
   186  	if err != nil {
   187  		// Log errors and return "" as MinIO can be deployed
   188  		// without Helm charts as well.
   189  		if !osIsNotExist(err) {
   190  			reqInfo := (&logger.ReqInfo{}).AppendTags("helmInfoFilePath", helmInfoFilePath)
   191  			ctx := logger.SetReqInfo(GlobalContext, reqInfo)
   192  			logger.LogIf(ctx, err)
   193  		}
   194  		return ""
   195  	}
   196  	defer helmInfoFile.Close()
   197  	scanner := bufio.NewScanner(helmInfoFile)
   198  	for scanner.Scan() {
   199  		if strings.Contains(scanner.Text(), "chart=") {
   200  			helmChartVersion := strings.TrimPrefix(scanner.Text(), "chart=")
   201  			// remove quotes from the chart version
   202  			return strings.Trim(helmChartVersion, `"`)
   203  		}
   204  	}
   205  
   206  	return ""
   207  }
   208  
   209  // IsSourceBuild - returns if this binary is a non-official build from
   210  // source code.
   211  func IsSourceBuild() bool {
   212  	_, err := minioVersionToReleaseTime(Version)
   213  	return err != nil
   214  }
   215  
   216  // IsPCFTile returns if server is running in PCF
   217  func IsPCFTile() bool {
   218  	return env.Get("MINIO_PCF_TILE_VERSION", "") != ""
   219  }
   220  
   221  // DO NOT CHANGE USER AGENT STYLE.
   222  // The style should be
   223  //
   224  //	MinIO (<OS>; <ARCH>[; <MODE>][; dcos][; kubernetes][; docker][; source]) MinIO/<VERSION> MinIO/<RELEASE-TAG> MinIO/<COMMIT-ID> [MinIO/universe-<PACKAGE-NAME>] [MinIO/helm-<HELM-VERSION>]
   225  //
   226  // Any change here should be discussed by opening an issue at
   227  // https://github.com/minio/minio/issues.
   228  func getUserAgent(mode string) string {
   229  	userAgentParts := []string{}
   230  	// Helper function to concisely append a pair of strings to a
   231  	// the user-agent slice.
   232  	uaAppend := func(p, q string) {
   233  		userAgentParts = append(userAgentParts, p, q)
   234  	}
   235  	uaAppend(MinioUAName, " (")
   236  	uaAppend("", runtime.GOOS)
   237  	uaAppend("; ", runtime.GOARCH)
   238  	if mode != "" {
   239  		uaAppend("; ", mode)
   240  	}
   241  	if IsDCOS() {
   242  		uaAppend("; ", "dcos")
   243  	}
   244  	if IsKubernetes() {
   245  		uaAppend("; ", "kubernetes")
   246  	}
   247  	if IsDocker() {
   248  		uaAppend("; ", "docker")
   249  	}
   250  	if IsBOSH() {
   251  		uaAppend("; ", "bosh")
   252  	}
   253  	if IsSourceBuild() {
   254  		uaAppend("; ", "source")
   255  	}
   256  
   257  	uaAppend(" ", Version)
   258  	uaAppend(" ", ReleaseTag)
   259  	uaAppend(" ", CommitID)
   260  	if IsDCOS() {
   261  		universePkgVersion := env.Get("MARATHON_APP_LABEL_DCOS_PACKAGE_VERSION", "")
   262  		// On DC/OS environment try to the get universe package version.
   263  		if universePkgVersion != "" {
   264  			uaAppend(" universe-", universePkgVersion)
   265  		}
   266  	}
   267  
   268  	if IsKubernetes() {
   269  		// In Kubernetes environment, try to fetch the helm package version
   270  		helmChartVersion := getHelmVersion("/podinfo/labels")
   271  		if helmChartVersion != "" {
   272  			uaAppend(" helm-", helmChartVersion)
   273  		}
   274  		// In Kubernetes environment, try to fetch the Operator, VSPHERE plugin version
   275  		opVersion := env.Get("MINIO_OPERATOR_VERSION", "")
   276  		if opVersion != "" {
   277  			uaAppend(" operator-", opVersion)
   278  		}
   279  		vsphereVersion := env.Get("MINIO_VSPHERE_PLUGIN_VERSION", "")
   280  		if vsphereVersion != "" {
   281  			uaAppend(" vsphere-plugin-", vsphereVersion)
   282  		}
   283  	}
   284  
   285  	if IsPCFTile() {
   286  		pcfTileVersion := env.Get("MINIO_PCF_TILE_VERSION", "")
   287  		if pcfTileVersion != "" {
   288  			uaAppend(" pcf-tile-", pcfTileVersion)
   289  		}
   290  	}
   291  	uaAppend("; ", "")
   292  
   293  	if cpus, err := gopsutilcpu.Info(); err == nil && len(cpus) > 0 {
   294  		cpuMap := make(map[string]struct{}, len(cpus))
   295  		coreMap := make(map[string]struct{}, len(cpus))
   296  		for i := range cpus {
   297  			cpuMap[cpus[i].PhysicalID] = struct{}{}
   298  			coreMap[cpus[i].CoreID] = struct{}{}
   299  		}
   300  		cpu := cpus[0]
   301  		uaAppend(" CPU ", fmt.Sprintf("(total_cpus:%d, total_cores:%d; vendor:%s; family:%s; model:%s; stepping:%d; model_name:%s)",
   302  			len(cpuMap), len(coreMap), cpu.VendorID, cpu.Family, cpu.Model, cpu.Stepping, cpu.ModelName))
   303  	}
   304  	uaAppend(")", "")
   305  
   306  	return strings.Join(userAgentParts, "")
   307  }
   308  
   309  func downloadReleaseURL(u *url.URL, timeout time.Duration, mode string) (content string, err error) {
   310  	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
   311  	if err != nil {
   312  		return content, AdminError{
   313  			Code:       AdminUpdateUnexpectedFailure,
   314  			Message:    err.Error(),
   315  			StatusCode: http.StatusInternalServerError,
   316  		}
   317  	}
   318  	req.Header.Set("User-Agent", getUserAgent(mode))
   319  
   320  	client := &http.Client{Transport: getUpdateTransport(timeout)}
   321  	resp, err := client.Do(req)
   322  	if err != nil {
   323  		if xnet.IsNetworkOrHostDown(err, false) {
   324  			return content, AdminError{
   325  				Code:       AdminUpdateURLNotReachable,
   326  				Message:    err.Error(),
   327  				StatusCode: http.StatusServiceUnavailable,
   328  			}
   329  		}
   330  		return content, AdminError{
   331  			Code:       AdminUpdateUnexpectedFailure,
   332  			Message:    err.Error(),
   333  			StatusCode: http.StatusInternalServerError,
   334  		}
   335  	}
   336  	if resp == nil {
   337  		return content, AdminError{
   338  			Code:       AdminUpdateUnexpectedFailure,
   339  			Message:    fmt.Sprintf("No response from server to download URL %s", u),
   340  			StatusCode: http.StatusInternalServerError,
   341  		}
   342  	}
   343  	defer xhttp.DrainBody(resp.Body)
   344  
   345  	if resp.StatusCode != http.StatusOK {
   346  		return content, AdminError{
   347  			Code:       AdminUpdateUnexpectedFailure,
   348  			Message:    fmt.Sprintf("Error downloading URL %s. Response: %v", u, resp.Status),
   349  			StatusCode: resp.StatusCode,
   350  		}
   351  	}
   352  
   353  	contentBytes, err := io.ReadAll(resp.Body)
   354  	if err != nil {
   355  		return content, AdminError{
   356  			Code:       AdminUpdateUnexpectedFailure,
   357  			Message:    fmt.Sprintf("Error reading response. %s", err),
   358  			StatusCode: http.StatusInternalServerError,
   359  		}
   360  	}
   361  
   362  	return string(contentBytes), nil
   363  }
   364  
   365  func releaseInfoToReleaseTime(releaseInfo string) (releaseTime time.Time, err error) {
   366  	// Split release of style minio.RELEASE.2019-08-21T19-40-07Z.<hotfix>
   367  	nfields := strings.SplitN(releaseInfo, ".", 2)
   368  	if len(nfields) != 2 {
   369  		err = fmt.Errorf("Unknown release information `%s`", releaseInfo)
   370  		return releaseTime, err
   371  	}
   372  	if nfields[0] != "minio" {
   373  		err = fmt.Errorf("Unknown release `%s`", releaseInfo)
   374  		return releaseTime, err
   375  	}
   376  
   377  	releaseTime, err = releaseTagToReleaseTime(nfields[1])
   378  	if err != nil {
   379  		err = fmt.Errorf("Unknown release tag format. %w", err)
   380  	}
   381  	return releaseTime, err
   382  }
   383  
   384  // parseReleaseData - parses release info file content fetched from
   385  // official minio download server.
   386  //
   387  // The expected format is a single line with two words like:
   388  //
   389  // fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z.<hotfix_optional>
   390  //
   391  // The second word must be `minio.` appended to a standard release tag.
   392  func parseReleaseData(data string) (sha256Sum []byte, releaseTime time.Time, releaseInfo string, err error) {
   393  	defer func() {
   394  		if err != nil {
   395  			err = AdminError{
   396  				Code:       AdminUpdateUnexpectedFailure,
   397  				Message:    err.Error(),
   398  				StatusCode: http.StatusInternalServerError,
   399  			}
   400  		}
   401  	}()
   402  
   403  	fields := strings.Fields(data)
   404  	if len(fields) != 2 {
   405  		err = fmt.Errorf("Unknown release data `%s`", data)
   406  		return sha256Sum, releaseTime, releaseInfo, err
   407  	}
   408  
   409  	sha256Sum, err = hex.DecodeString(fields[0])
   410  	if err != nil {
   411  		return sha256Sum, releaseTime, releaseInfo, err
   412  	}
   413  
   414  	releaseInfo = fields[1]
   415  
   416  	releaseTime, err = releaseInfoToReleaseTime(releaseInfo)
   417  	return sha256Sum, releaseTime, releaseInfo, err
   418  }
   419  
   420  func getUpdateTransport(timeout time.Duration) http.RoundTripper {
   421  	var updateTransport http.RoundTripper = &http.Transport{
   422  		Proxy:                 http.ProxyFromEnvironment,
   423  		DialContext:           xhttp.NewCustomDialContext(timeout, globalTCPOptions),
   424  		IdleConnTimeout:       timeout,
   425  		TLSHandshakeTimeout:   timeout,
   426  		ExpectContinueTimeout: timeout,
   427  		TLSClientConfig: &tls.Config{
   428  			RootCAs:            globalRootCAs,
   429  			ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize),
   430  		},
   431  		DisableCompression: true,
   432  	}
   433  	return updateTransport
   434  }
   435  
   436  func getLatestReleaseTime(u *url.URL, timeout time.Duration, mode string) (sha256Sum []byte, releaseTime time.Time, err error) {
   437  	data, err := downloadReleaseURL(u, timeout, mode)
   438  	if err != nil {
   439  		return sha256Sum, releaseTime, err
   440  	}
   441  
   442  	sha256Sum, releaseTime, _, err = parseReleaseData(data)
   443  	return
   444  }
   445  
   446  const (
   447  	// Kubernetes deployment doc link.
   448  	kubernetesDeploymentDoc = "https://min.io/docs/minio/kubernetes/upstream/index.html#quickstart-for-kubernetes"
   449  
   450  	// Mesos deployment doc link.
   451  	mesosDeploymentDoc = "https://min.io/docs/minio/kubernetes/upstream/index.html#quickstart-for-kubernetes"
   452  )
   453  
   454  func getDownloadURL(releaseTag string) (downloadURL string) {
   455  	// Check if we are in DCOS environment, return
   456  	// deployment guide for update procedures.
   457  	if IsDCOS() {
   458  		return mesosDeploymentDoc
   459  	}
   460  
   461  	// Check if we are in kubernetes environment, return
   462  	// deployment guide for update procedures.
   463  	if IsKubernetes() {
   464  		return kubernetesDeploymentDoc
   465  	}
   466  
   467  	// Check if we are docker environment, return docker update command
   468  	if IsDocker() {
   469  		// Construct release tag name.
   470  		return fmt.Sprintf("podman pull quay.io/minio/minio:%s", releaseTag)
   471  	}
   472  
   473  	// For binary only installations, we return link to the latest binary.
   474  	if runtime.GOOS == "windows" {
   475  		return MinioReleaseURL + "minio.exe"
   476  	}
   477  
   478  	return MinioReleaseURL + "minio"
   479  }
   480  
   481  func getUpdateReaderFromURL(u *url.URL, transport http.RoundTripper, mode string) (io.ReadCloser, error) {
   482  	clnt := &http.Client{
   483  		Transport: transport,
   484  	}
   485  	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
   486  	if err != nil {
   487  		return nil, AdminError{
   488  			Code:       AdminUpdateUnexpectedFailure,
   489  			Message:    err.Error(),
   490  			StatusCode: http.StatusInternalServerError,
   491  		}
   492  	}
   493  
   494  	req.Header.Set("User-Agent", getUserAgent(mode))
   495  
   496  	resp, err := clnt.Do(req)
   497  	if err != nil {
   498  		if xnet.IsNetworkOrHostDown(err, false) {
   499  			return nil, AdminError{
   500  				Code:       AdminUpdateURLNotReachable,
   501  				Message:    err.Error(),
   502  				StatusCode: http.StatusServiceUnavailable,
   503  			}
   504  		}
   505  		return nil, AdminError{
   506  			Code:       AdminUpdateUnexpectedFailure,
   507  			Message:    err.Error(),
   508  			StatusCode: http.StatusInternalServerError,
   509  		}
   510  	}
   511  	return resp.Body, nil
   512  }
   513  
   514  var updateInProgress atomic.Uint32
   515  
   516  // Function to get the reader from an architecture
   517  func downloadBinary(u *url.URL, mode string) (binCompressed []byte, bin []byte, err error) {
   518  	transport := getUpdateTransport(30 * time.Second)
   519  	var reader io.ReadCloser
   520  	if u.Scheme == "https" || u.Scheme == "http" {
   521  		reader, err = getUpdateReaderFromURL(u, transport, mode)
   522  		if err != nil {
   523  			return nil, nil, err
   524  		}
   525  	} else {
   526  		return nil, nil, fmt.Errorf("unsupported protocol scheme: %s", u.Scheme)
   527  	}
   528  	defer xhttp.DrainBody(reader)
   529  
   530  	b := bytebufferpool.Get()
   531  	bc := bytebufferpool.Get()
   532  	defer func() {
   533  		b.Reset()
   534  		bc.Reset()
   535  
   536  		bytebufferpool.Put(b)
   537  		bytebufferpool.Put(bc)
   538  	}()
   539  
   540  	w, err := zstd.NewWriter(bc)
   541  	if err != nil {
   542  		return nil, nil, err
   543  	}
   544  
   545  	if _, err = io.Copy(w, io.TeeReader(reader, b)); err != nil {
   546  		return nil, nil, err
   547  	}
   548  
   549  	w.Close()
   550  	return bc.Bytes(), b.Bytes(), nil
   551  }
   552  
   553  const (
   554  	// Update this whenever the official minisign pubkey is rotated.
   555  	defaultMinisignPubkey = "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
   556  )
   557  
   558  func verifyBinary(u *url.URL, sha256Sum []byte, releaseInfo, mode string, reader io.Reader) (err error) {
   559  	if !updateInProgress.CompareAndSwap(0, 1) {
   560  		return errors.New("update already in progress")
   561  	}
   562  	defer updateInProgress.Store(0)
   563  
   564  	transport := getUpdateTransport(30 * time.Second)
   565  	opts := selfupdate.Options{
   566  		Hash:     crypto.SHA256,
   567  		Checksum: sha256Sum,
   568  	}
   569  
   570  	if err := opts.CheckPermissions(); err != nil {
   571  		return AdminError{
   572  			Code:       AdminUpdateApplyFailure,
   573  			Message:    fmt.Sprintf("server update failed with: %s, do not restart the servers yet", err),
   574  			StatusCode: http.StatusInternalServerError,
   575  		}
   576  	}
   577  
   578  	minisignPubkey := env.Get(envMinisignPubKey, defaultMinisignPubkey)
   579  	if minisignPubkey != "" {
   580  		v := selfupdate.NewVerifier()
   581  		u.Path = path.Dir(u.Path) + slashSeparator + releaseInfo + ".minisig"
   582  		if err = v.LoadFromURL(u.String(), minisignPubkey, transport); err != nil {
   583  			return AdminError{
   584  				Code:       AdminUpdateApplyFailure,
   585  				Message:    fmt.Sprintf("signature loading failed for %v with %v", u, err),
   586  				StatusCode: http.StatusInternalServerError,
   587  			}
   588  		}
   589  		opts.Verifier = v
   590  	}
   591  
   592  	if err = selfupdate.PrepareAndCheckBinary(reader, opts); err != nil {
   593  		var pathErr *os.PathError
   594  		if errors.As(err, &pathErr) {
   595  			return AdminError{
   596  				Code: AdminUpdateApplyFailure,
   597  				Message: fmt.Sprintf("Unable to update the binary at %s: %v",
   598  					filepath.Dir(pathErr.Path), pathErr.Err),
   599  				StatusCode: http.StatusForbidden,
   600  			}
   601  		}
   602  		return AdminError{
   603  			Code:       AdminUpdateApplyFailure,
   604  			Message:    err.Error(),
   605  			StatusCode: http.StatusInternalServerError,
   606  		}
   607  	}
   608  
   609  	return nil
   610  }
   611  
   612  func commitBinary() (err error) {
   613  	if !updateInProgress.CompareAndSwap(0, 1) {
   614  		return errors.New("update already in progress")
   615  	}
   616  	defer updateInProgress.Store(0)
   617  
   618  	opts := selfupdate.Options{}
   619  
   620  	if err = selfupdate.CommitBinary(opts); err != nil {
   621  		if rerr := selfupdate.RollbackError(err); rerr != nil {
   622  			return AdminError{
   623  				Code:       AdminUpdateApplyFailure,
   624  				Message:    fmt.Sprintf("Failed to rollback from bad update: %v", rerr),
   625  				StatusCode: http.StatusInternalServerError,
   626  			}
   627  		}
   628  		var pathErr *os.PathError
   629  		if errors.As(err, &pathErr) {
   630  			return AdminError{
   631  				Code: AdminUpdateApplyFailure,
   632  				Message: fmt.Sprintf("Unable to update the binary at %s: %v",
   633  					filepath.Dir(pathErr.Path), pathErr.Err),
   634  				StatusCode: http.StatusForbidden,
   635  			}
   636  		}
   637  		return AdminError{
   638  			Code:       AdminUpdateApplyFailure,
   639  			Message:    err.Error(),
   640  			StatusCode: http.StatusInternalServerError,
   641  		}
   642  	}
   643  
   644  	return nil
   645  }