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 }