github.com/0chain/gosdk@v1.17.11/wasmsdk/updateImage.go (about)

     1  //go:build js && wasm
     2  // +build js,wasm
     3  
     4  package main
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"net/http"
    13  	"strings"
    14  )
    15  
    16  type AuthRequest struct {
    17  	Password string `json:"password"`
    18  	Username string `json:"username"`
    19  }
    20  
    21  type AuthResponse struct {
    22  	Jwt string `json:"jwt"`
    23  }
    24  
    25  type Endpoint struct {
    26  	Id int `json:"Id"`
    27  }
    28  
    29  const (
    30  	AUTH       = "/api/auth"
    31  	ENDPOINTS  = "/api/endpoints"
    32  	CONTAINERS = "/docker/containers/"
    33  	PULLIMAGE  = "/docker/images/create?fromImage="
    34  )
    35  
    36  // --- exposed functions ---
    37  // UpdateContainer update the given container ID with a new image ID in a given domain
    38  // The domain should expose the docker API endpoints under `/endpoints/{endpointID}/docker`
    39  // The request should be authenticated with the given username and password, by first creating an auth token then issuing the request.
    40  //
    41  // Parameters:
    42  //   - username is the username to authenticate with
    43  //   - password is the password to authenticate with
    44  //   - domain is the domain to issue the request to
    45  //   - containerID is the ID of the container to update
    46  //   - NewImageID is the ID of the new image to update the container with
    47  func UpdateContainer(username, password, domain, containerID, NewImageID string) (map[string]interface{}, error) {
    48  	sdkLogger.Info("generating authtoken")
    49  	authToken, err := getToken(username, password, domain)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	// get endpoint ID
    55  	id, err := getEndpointId(authToken, domain)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	endpointID := fmt.Sprintf("/%d", id)
    60  
    61  	sdkLogger.Info("pulling the new image...")
    62  	url := domain + ENDPOINTS + endpointID + PULLIMAGE + NewImageID
    63  	_, err = pullImage(authToken, domain, url)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	// stopContainer
    69  	sdkLogger.Info("stopping the container..")
    70  	url = domain + ENDPOINTS + endpointID + CONTAINERS + containerID + "/stop"
    71  	_, err = stopContainer(authToken, domain, url)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	sdkLogger.Info("getting container Object")
    77  	container, err := getContainer(authToken, domain, endpointID, containerID)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	containerName := container.Name
    82  	containerReq, err := generateContainerObj(container)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	// renameContainer
    88  	sdkLogger.Info("renaming container...")
    89  	url = domain + ENDPOINTS + endpointID + CONTAINERS + containerID + "/rename?name=" + containerName + "-old"
    90  	_, err = renameContainer(authToken, url)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	// createContainer
    96  	sdkLogger.Info("creating a new container with image: ", NewImageID)
    97  	containerReq.Image = NewImageID
    98  	url = domain + ENDPOINTS + endpointID + CONTAINERS + "/create?name=" + containerName
    99  	newContainer, err := createContainer(authToken, url, containerReq)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	newContainerID := newContainer["Id"].(string)
   104  
   105  	// startContainer //204 no content
   106  	sdkLogger.Info("starting starting the new container", newContainerID)
   107  	_, err = startContainer(authToken, domain, newContainerID, endpointID)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	// delete old container
   113  	sdkLogger.Info("deleting old container..")
   114  	err = deleteContainer(authToken, domain, containerID, endpointID)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	return newContainer, nil
   120  }
   121  
   122  // GetContainers returns all the running containers in a given domain exposing the `/endpoints/{endpointID}/docker/containers/json` endpoint
   123  // The request should be authenticated with the given username and password, by first creating an auth token then issuing the request.
   124  // The response is a list of containers in JSON format.
   125  //
   126  // Parameters:
   127  //   - username is the username to authenticate with
   128  //   - password is the password to authenticate with
   129  //   - domain is the domain to issue the request to
   130  func GetContainers(username, password, domain string) ([]*map[string]interface{}, error) {
   131  	authToken, err := getToken(username, password, domain)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	id, err := getEndpointId(authToken, domain)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	endpointID := fmt.Sprintf("/%d", id)
   142  	domain = strings.TrimRight(domain, "/")
   143  	url := domain + ENDPOINTS + endpointID + CONTAINERS + "json?all=1"
   144  	body, status, err := doGetRequest(authToken, url)
   145  	if err != nil {
   146  		sdkLogger.Error("Error reading response body:", err)
   147  		return nil, err
   148  	}
   149  	if status == http.StatusOK {
   150  		var containers []*map[string]interface{}
   151  		err = json.Unmarshal(body, &containers)
   152  		if err != nil {
   153  			sdkLogger.Error("Error decoding JSON:", err)
   154  			return nil, err
   155  		}
   156  		return containers, nil
   157  	}
   158  	return nil, fmt.Errorf("returned response %d. Body: %s", status, string(body))
   159  }
   160  
   161  // SearchContainer search a container with a given name in a given domain exposing the `/endpoints/{endpointID}/docker/containers/json` endpoint
   162  // The request should be authenticated with the given username and password, by first creating an auth token then issuing the request.
   163  // The response is a list of containers in JSON format that match the given name.
   164  //
   165  // Parameters:
   166  //   - username is the username to authenticate with
   167  //   - password is the password to authenticate with
   168  //   - domain is the domain to issue the request to
   169  //   - name is the name of the container to search for
   170  func SearchContainer(username, password, domain, name string) ([]*map[string]interface{}, error) {
   171  	authToken, err := getToken(username, password, domain)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	id, err := getEndpointId(authToken, domain)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	endpointID := fmt.Sprintf("/%d", id)
   181  	domain = strings.TrimRight(domain, "/")
   182  
   183  	// the search regex start with ^/blobber_ because the blobber config files reside in blobber folder
   184  	// https://github.com/0chain/zcnwebappscripts/blob/main/chimney.sh#L18
   185  	url := domain + ENDPOINTS + endpointID + CONTAINERS + fmt.Sprintf("json?all=1&filters={\"name\":[\"^/%s*\"]}", name)
   186  	return searchContainerInternal(authToken, url)
   187  }
   188  
   189  // --- helper functions ----
   190  
   191  func getEndpointId(authToken, domain string) (int, error) {
   192  	url := domain + ENDPOINTS
   193  	body, status, err := doGetRequest(authToken, url)
   194  	if err != nil {
   195  		sdkLogger.Error("Error reading response body:", err)
   196  		return 0, err
   197  	}
   198  
   199  	var endpoints []Endpoint
   200  	if status == http.StatusOK {
   201  		err = json.Unmarshal(body, &endpoints)
   202  		if err != nil {
   203  			sdkLogger.Error("Error decoding endpoints:", err)
   204  			return 0, err
   205  		}
   206  
   207  		if len(endpoints) > 0 {
   208  			return endpoints[0].Id, nil
   209  		}
   210  	}
   211  	return 0, fmt.Errorf("returned response %d. Body: %s", status, string(body))
   212  }
   213  
   214  // getContainer gets a container object by ID
   215  func getContainer(authToken, domain, endpointID, containerID string) (*GetContainerResp, error) {
   216  	url := domain + ENDPOINTS + endpointID + CONTAINERS + containerID + "/json"
   217  	body, status, err := doGetRequest(authToken, url)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	if status == http.StatusOK {
   223  		var container GetContainerResp
   224  		err = json.Unmarshal(body, &container)
   225  		if err != nil {
   226  			sdkLogger.Error("Error decoding JSON:", err)
   227  			return nil, err
   228  		}
   229  		return &container, nil
   230  	}
   231  	return nil, fmt.Errorf("returned response %d. Body: %s", status, string(body))
   232  }
   233  
   234  func searchContainerInternal(authToken, url string) ([]*map[string]interface{}, error) {
   235  	body, status, err := doGetRequest(authToken, url)
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  	if status == http.StatusOK {
   240  		var containers []*map[string]interface{}
   241  		err = json.Unmarshal(body, &containers)
   242  		if err != nil {
   243  			sdkLogger.Error("Error decoding JSON:", err)
   244  			return nil, err
   245  		}
   246  		return containers, err
   247  	}
   248  	return nil, fmt.Errorf("returned response %d. Body: %s", status, string(body))
   249  }
   250  
   251  func getToken(username, password, domain string) (string, error) {
   252  	// get AuthToken
   253  	authData := AuthRequest{
   254  		Password: password,
   255  		Username: username,
   256  	}
   257  
   258  	url := domain + AUTH
   259  	jsonData, err := json.Marshal(authData)
   260  	if err != nil {
   261  		sdkLogger.Error("Error marshaling JSON:", err)
   262  		return "", err
   263  	}
   264  
   265  	body, status, err := doHTTPRequest("POST", url, "", bytes.NewBuffer(jsonData))
   266  	if err != nil {
   267  		return "", err
   268  	}
   269  	if status == http.StatusOK {
   270  		var response AuthResponse
   271  		err = json.Unmarshal(body, &response)
   272  		if err != nil {
   273  			sdkLogger.Error("Error decoding JSON:", err)
   274  			return "", err
   275  		}
   276  
   277  		jwt := response.Jwt
   278  		return jwt, nil
   279  	}
   280  	return "", fmt.Errorf("returned response %d. Body: %s", status, string(body))
   281  }
   282  
   283  func doGetRequest(authToken, url string) ([]byte, int, error) {
   284  	return doHTTPRequest("GET", url, authToken, nil)
   285  }
   286  
   287  func doHTTPRequest(method, url, authToken string, body io.Reader) ([]byte, int, error) {
   288  	req, err := http.NewRequest(method, url, body)
   289  	if err != nil {
   290  		sdkLogger.Error("Error creating HTTP request:", err)
   291  		return nil, 0, err
   292  	}
   293  
   294  	req.Header.Set("Authorization", authToken)
   295  	req.Header.Set("Connection", "keep-alive")
   296  	req.Header.Set("Content-Type", "application/json")
   297  
   298  	client := &http.Client{}
   299  	resp, err := client.Do(req)
   300  	if err != nil {
   301  		sdkLogger.Error("Error sending HTTP request:", err)
   302  		return nil, 0, err
   303  	}
   304  	defer resp.Body.Close()
   305  	respBody, err := ioutil.ReadAll(resp.Body)
   306  	return respBody, resp.StatusCode, err
   307  }
   308  
   309  func doPostRequest(url, authToken string, reqBody io.Reader) (map[string]interface{}, error) {
   310  	body, statusCode, err := doHTTPRequest("POST", url, authToken, reqBody)
   311  	if err != nil {
   312  		return nil, err
   313  	}
   314  
   315  	if statusCode == http.StatusOK || statusCode == http.StatusNoContent || statusCode == http.StatusNotModified {
   316  		var resp map[string]interface{}
   317  		err = json.Unmarshal(body, &resp)
   318  		if err != nil {
   319  			// ignore if unmarhalling fails
   320  			sdkLogger.Info("failed to unmarshall:", err)
   321  			return nil, nil
   322  		}
   323  		return resp, nil
   324  	}
   325  
   326  	var respMsg struct {
   327  		Message string `json:"message"`
   328  	}
   329  	err = json.Unmarshal(body, &respMsg)
   330  	if err != nil {
   331  		return nil, fmt.Errorf("got responsebody %s, with statuscode %d", string(body), statusCode)
   332  	}
   333  
   334  	return nil, fmt.Errorf(respMsg.Message)
   335  }
   336  
   337  func deleteContainer(authToken, domain, containerID, endpointID string) error {
   338  	url := domain + ENDPOINTS + endpointID + CONTAINERS + containerID + "?force=true"
   339  
   340  	body, status, err := doHTTPRequest("DELETE", url, authToken, nil)
   341  	if err != nil {
   342  		return err
   343  	}
   344  	if status == http.StatusOK || status == http.StatusNoContent {
   345  		return nil
   346  	}
   347  	var respMsg struct {
   348  		Message string `json:"message"`
   349  	}
   350  	err = json.Unmarshal(body, &respMsg)
   351  	if err != nil {
   352  		return fmt.Errorf("got responsebody %s, with statuscode %d", string(body), status)
   353  	}
   354  
   355  	return fmt.Errorf(respMsg.Message)
   356  }
   357  
   358  func pullImage(authToken, domain, url string) (map[string]interface{}, error) {
   359  	return doPostRequest(url, authToken, nil)
   360  }
   361  
   362  func stopContainer(authToken, domain, url string) (map[string]interface{}, error) {
   363  	return doPostRequest(url, authToken, nil)
   364  }
   365  
   366  func renameContainer(authToken, url string) (map[string]interface{}, error) {
   367  	return doPostRequest(url, authToken, nil)
   368  }
   369  
   370  func startContainer(authToken, domain, containerID, endpointID string) (map[string]interface{}, error) {
   371  	url := domain + ENDPOINTS + endpointID + CONTAINERS + containerID + "/start"
   372  	return doPostRequest(url, authToken, nil)
   373  }
   374  
   375  type NetworkSettings struct {
   376  	Networks map[string]EndpointConfig `json:"Networks"`
   377  }
   378  
   379  type GetContainerResp struct {
   380  	Config          Body            `json:"Config"`
   381  	HostConfig      HostConfig      `json:"HostConfig"`
   382  	Name            string          `json:"Name"`
   383  	NetworkSettings NetworkSettings `json:"NetworkSettings"`
   384  }
   385  
   386  func generateContainerObj(container *GetContainerResp) (*Body, error) {
   387  	// var config = angular.copy($scope.config);
   388  	var config Body
   389  	config = container.Config
   390  	config.HostConfig = container.HostConfig
   391  	config.NetworkingConfig.EndpointsConfig = container.NetworkSettings.Networks
   392  	return &config, nil
   393  }
   394  
   395  func createContainer(authToken, url string, container *Body) (map[string]interface{}, error) {
   396  	reqBodyJSON, err := json.Marshal(container)
   397  	if err != nil {
   398  		sdkLogger.Error("Error marshaling request body:", err)
   399  		return nil, err
   400  	}
   401  
   402  	return doPostRequest(url, authToken, bytes.NewBuffer(reqBodyJSON))
   403  }
   404  
   405  type Body struct {
   406  	Hostname         string                 `json:"Hostname"`
   407  	Domainname       string                 `json:"Domainname"`
   408  	User             string                 `json:"User"`
   409  	AttachStdin      bool                   `json:"AttachStdin"`
   410  	AttachStdout     bool                   `json:"AttachStdout"`
   411  	AttachStderr     bool                   `json:"AttachStderr"`
   412  	Tty              bool                   `json:"Tty"`
   413  	OpenStdin        bool                   `json:"OpenStdin"`
   414  	StdinOnce        bool                   `json:"StdinOnce"`
   415  	Env              []string               `json:"Env"`
   416  	Cmd              []string               `json:"Cmd"`
   417  	Entrypoint       string                 `json:"Entrypoint"`
   418  	Image            string                 `json:"Image"`
   419  	Labels           map[string]string      `json:"Labels"`
   420  	Volumes          map[string]interface{} `json:"Volumes"`
   421  	WorkingDir       string                 `json:"WorkingDir"`
   422  	NetworkDisabled  bool                   `json:"NetworkDisabled"`
   423  	MacAddress       string                 `json:"MacAddress"`
   424  	ExposedPorts     map[string]interface{} `json:"ExposedPorts"`
   425  	StopSignal       string                 `json:"StopSignal"`
   426  	StopTimeout      int                    `json:"StopTimeout"`
   427  	HostConfig       HostConfig             `json:"HostConfig"`
   428  	NetworkingConfig NetworkingConfig       `json:"NetworkingConfig"`
   429  }
   430  
   431  type HostConfig struct {
   432  	Binds                []string                 `json:"Binds"`
   433  	Links                []string                 `json:"Links"`
   434  	Memory               int                      `json:"Memory"`
   435  	MemorySwap           int                      `json:"MemorySwap"`
   436  	MemoryReservation    int                      `json:"MemoryReservation"`
   437  	KernelMemory         int                      `json:"KernelMemory"`
   438  	NanoCpus             int                      `json:"NanoCpus"`
   439  	CpuPercent           int                      `json:"CpuPercent"`
   440  	CpuShares            int                      `json:"CpuShares"`
   441  	CpuPeriod            int                      `json:"CpuPeriod"`
   442  	CpuRealtimePeriod    int                      `json:"CpuRealtimePeriod"`
   443  	CpuRealtimeRuntime   int                      `json:"CpuRealtimeRuntime"`
   444  	CpuQuota             int                      `json:"CpuQuota"`
   445  	CpusetCpus           string                   `json:"CpusetCpus"`
   446  	CpusetMems           string                   `json:"CpusetMems"`
   447  	MaximumIOps          int                      `json:"MaximumIOps"`
   448  	MaximumIOBps         int                      `json:"MaximumIOBps"`
   449  	BlkioWeight          int                      `json:"BlkioWeight"`
   450  	BlkioWeightDevice    []interface{}            `json:"BlkioWeightDevice"`
   451  	BlkioDeviceReadBps   []interface{}            `json:"BlkioDeviceReadBps"`
   452  	BlkioDeviceReadIOps  []interface{}            `json:"BlkioDeviceReadIOps"`
   453  	BlkioDeviceWriteBps  []interface{}            `json:"BlkioDeviceWriteBps"`
   454  	BlkioDeviceWriteIOps []interface{}            `json:"BlkioDeviceWriteIOps"`
   455  	MemorySwappiness     int                      `json:"MemorySwappiness"`
   456  	OomKillDisable       bool                     `json:"OomKillDisable"`
   457  	OomScoreAdj          int                      `json:"OomScoreAdj"`
   458  	PidMode              string                   `json:"PidMode"`
   459  	PidsLimit            int                      `json:"PidsLimit"`
   460  	PortBindings         map[string][]PortBinding `json:"PortBindings"`
   461  	PublishAllPorts      bool                     `json:"PublishAllPorts"`
   462  	Privileged           bool                     `json:"Privileged"`
   463  	ReadonlyRootfs       bool                     `json:"ReadonlyRootfs"`
   464  	Dns                  []string                 `json:"Dns"`
   465  	DnsOptions           []string                 `json:"DnsOptions"`
   466  	DnsSearch            []string                 `json:"DnsSearch"`
   467  	VolumesFrom          []string                 `json:"VolumesFrom"`
   468  	CapAdd               []string                 `json:"CapAdd"`
   469  	CapDrop              []string                 `json:"CapDrop"`
   470  	GroupAdd             []string                 `json:"GroupAdd"`
   471  	RestartPolicy        RestartPolicy            `json:"RestartPolicy"`
   472  	AutoRemove           bool                     `json:"AutoRemove"`
   473  	NetworkMode          string                   `json:"NetworkMode"`
   474  	Devices              []interface{}            `json:"Devices"`
   475  	Ulimits              []interface{}            `json:"Ulimits"`
   476  	LogConfig            LogConfig                `json:"LogConfig"`
   477  	SecurityOpt          []interface{}            `json:"SecurityOpt"`
   478  	StorageOpt           map[string]interface{}   `json:"StorageOpt"`
   479  	CgroupParent         string                   `json:"CgroupParent"`
   480  	VolumeDriver         string                   `json:"VolumeDriver"`
   481  	ShmSize              int                      `json:"ShmSize"`
   482  }
   483  
   484  type PortBinding struct {
   485  	HostPort string `json:"HostPort"`
   486  }
   487  
   488  type RestartPolicy struct {
   489  	Name              string `json:"Name"`
   490  	MaximumRetryCount int    `json:"MaximumRetryCount"`
   491  }
   492  
   493  type LogConfig struct {
   494  	Type   string                 `json:"Type"`
   495  	Config map[string]interface{} `json:"Config"`
   496  }
   497  
   498  type NetworkingConfig struct {
   499  	EndpointsConfig map[string]EndpointConfig `json:"EndpointsConfig"`
   500  }
   501  
   502  type EndpointConfig struct {
   503  	IPAMConfig IPAMConfig `json:"IPAMConfig"`
   504  	Links      []string   `json:"Links"`
   505  	Aliases    []string   `json:"Aliases"`
   506  }
   507  
   508  type IPAMConfig struct {
   509  	IPv4Address  string   `json:"IPv4Address"`
   510  	IPv6Address  string   `json:"IPv6Address"`
   511  	LinkLocalIPs []string `json:"LinkLocalIPs"`
   512  }