Add image creation from snapshot
Rename organization_id / access_key Update test / doc
This commit is contained in:
parent
9b611af7e6
commit
1fb13cc23e
@ -8,11 +8,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Artifact struct {
|
type Artifact struct {
|
||||||
|
// The name of the image
|
||||||
|
imageName string
|
||||||
|
|
||||||
|
// The ID of the image
|
||||||
|
imageID string
|
||||||
|
|
||||||
// The name of the snapshot
|
// The name of the snapshot
|
||||||
snapshotName string
|
snapshotName string
|
||||||
|
|
||||||
// The ID of the snapshot
|
// The ID of the snapshot
|
||||||
snapshotId string
|
snapshotID string
|
||||||
|
|
||||||
// The name of the region
|
// The name of the region
|
||||||
regionName string
|
regionName string
|
||||||
@ -31,11 +37,12 @@ func (*Artifact) Files() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Artifact) Id() string {
|
func (a *Artifact) Id() string {
|
||||||
return fmt.Sprintf("%s:%s", a.regionName, a.snapshotId)
|
return fmt.Sprintf("%s:%s", a.regionName, a.imageID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Artifact) String() string {
|
func (a *Artifact) String() string {
|
||||||
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v) in region '%v'", a.snapshotName, a.snapshotId, a.regionName)
|
return fmt.Sprintf("An image was created: '%v' (ID: %v) in region '%v' based on snapshot '%v' (ID: %v)",
|
||||||
|
a.imageName, a.imageID, a.regionName, a.snapshotName, a.snapshotID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
func (a *Artifact) State(name string) interface{} {
|
||||||
@ -43,7 +50,13 @@ func (a *Artifact) State(name string) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Artifact) Destroy() error {
|
func (a *Artifact) Destroy() error {
|
||||||
log.Printf("Destroying image: %s (%s)", a.snapshotId, a.snapshotName)
|
log.Printf("Destroying image: %s (%s)", a.imageID, a.imageName)
|
||||||
err := a.client.DeleteSnapshot(a.snapshotId)
|
if err := a.client.DeleteImage(a.imageID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
log.Printf("Destroying snapshot: %s (%s)", a.snapshotID, a.snapshotName)
|
||||||
|
if err := a.client.DeleteSnapshot(a.snapshotID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -15,8 +15,8 @@ func TestArtifact_Impl(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestArtifactId(t *testing.T) {
|
func TestArtifactId(t *testing.T) {
|
||||||
a := &Artifact{"packer-foobar", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil}
|
a := &Artifact{"packer-foobar-image", "cc586e45-5156-4f71-b223-cf406b10dd1d", "packer-foobar-snapshot", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil}
|
||||||
expected := "ams1:cc586e45-5156-4f71-b223-cf406b10dd1c"
|
expected := "ams1:cc586e45-5156-4f71-b223-cf406b10dd1d"
|
||||||
|
|
||||||
if a.Id() != expected {
|
if a.Id() != expected {
|
||||||
t.Fatalf("artifact ID should match: %v", expected)
|
t.Fatalf("artifact ID should match: %v", expected)
|
||||||
@ -24,8 +24,8 @@ func TestArtifactId(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestArtifactString(t *testing.T) {
|
func TestArtifactString(t *testing.T) {
|
||||||
a := &Artifact{"packer-foobar", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil}
|
a := &Artifact{"packer-foobar-image", "cc586e45-5156-4f71-b223-cf406b10dd1d", "packer-foobar-snapshot", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil}
|
||||||
expected := "A snapshot was created: 'packer-foobar' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c) in region 'ams1'"
|
expected := "An image was created: 'packer-foobar-image' (ID: cc586e45-5156-4f71-b223-cf406b10dd1d) in region 'ams1' based on snapshot 'packer-foobar-snapshot' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c)"
|
||||||
|
|
||||||
if a.String() != expected {
|
if a.String() != expected {
|
||||||
t.Fatalf("artifact string should match: %v", expected)
|
t.Fatalf("artifact string should match: %v", expected)
|
||||||
|
@ -56,6 +56,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||||||
new(common.StepProvision),
|
new(common.StepProvision),
|
||||||
new(stepShutdown),
|
new(stepShutdown),
|
||||||
new(stepSnapshot),
|
new(stepSnapshot),
|
||||||
|
new(stepImage),
|
||||||
new(stepTerminate),
|
new(stepTerminate),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,8 +73,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||||||
}
|
}
|
||||||
|
|
||||||
artifact := &Artifact{
|
artifact := &Artifact{
|
||||||
|
imageName: state.Get("image_name").(string),
|
||||||
|
imageID: state.Get("image_id").(string),
|
||||||
snapshotName: state.Get("snapshot_name").(string),
|
snapshotName: state.Get("snapshot_name").(string),
|
||||||
snapshotId: state.Get("snapshot_id").(string),
|
snapshotID: state.Get("snapshot_id").(string),
|
||||||
regionName: state.Get("region").(string),
|
regionName: state.Get("region").(string),
|
||||||
client: client,
|
client: client,
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func testConfig() map[string]interface{} {
|
func testConfig() map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"api_organization": "foo",
|
"api_access_key": "foo",
|
||||||
"api_token": "bar",
|
"api_token": "bar",
|
||||||
"region": "ams1",
|
"region": "ams1",
|
||||||
"commercial_type": "VC1S",
|
"commercial_type": "VC1S",
|
||||||
|
@ -19,13 +19,14 @@ type Config struct {
|
|||||||
Comm communicator.Config `mapstructure:",squash"`
|
Comm communicator.Config `mapstructure:",squash"`
|
||||||
|
|
||||||
Token string `mapstructure:"api_token"`
|
Token string `mapstructure:"api_token"`
|
||||||
Organization string `mapstructure:"api_organization"`
|
Organization string `mapstructure:"api_access_key"`
|
||||||
|
|
||||||
Region string `mapstructure:"region"`
|
Region string `mapstructure:"region"`
|
||||||
Image string `mapstructure:"image"`
|
Image string `mapstructure:"image"`
|
||||||
CommercialType string `mapstructure:"commercial_type"`
|
CommercialType string `mapstructure:"commercial_type"`
|
||||||
|
|
||||||
SnapshotName string `mapstructure:"snapshot_name"`
|
SnapshotName string `mapstructure:"snapshot_name"`
|
||||||
|
ImageName string `mapstructure:"image_name"`
|
||||||
ServerName string `mapstructure:"server_name"`
|
ServerName string `mapstructure:"server_name"`
|
||||||
|
|
||||||
UserAgent string
|
UserAgent string
|
||||||
@ -53,7 +54,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
|||||||
c.UserAgent = "Packer - Scaleway builder"
|
c.UserAgent = "Packer - Scaleway builder"
|
||||||
|
|
||||||
if c.Organization == "" {
|
if c.Organization == "" {
|
||||||
c.Organization = os.Getenv("SCALEWAY_API_ORGANIZATION")
|
c.Organization = os.Getenv("SCALEWAY_API_ACCESS_KEY")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Token == "" {
|
if c.Token == "" {
|
||||||
@ -61,7 +62,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.SnapshotName == "" {
|
if c.SnapshotName == "" {
|
||||||
def, err := interpolate.Render("packer-{{timestamp}}", nil)
|
def, err := interpolate.Render("snapshot-packer-{{timestamp}}", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -69,6 +70,15 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
|||||||
c.SnapshotName = def
|
c.SnapshotName = def
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.ImageName == "" {
|
||||||
|
def, err := interpolate.Render("image-packer-{{timestamp}}", nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ImageName = def
|
||||||
|
}
|
||||||
|
|
||||||
if c.ServerName == "" {
|
if c.ServerName == "" {
|
||||||
// Default to packer-[time-ordered-uuid]
|
// Default to packer-[time-ordered-uuid]
|
||||||
c.ServerName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
c.ServerName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||||
|
53
builder/scaleway/step_create_image.go
Normal file
53
builder/scaleway/step_create_image.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepImage struct{}
|
||||||
|
|
||||||
|
func (s *stepImage) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
client := state.Get("client").(*api.ScalewayAPI)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
c := state.Get("config").(Config)
|
||||||
|
snapshotID := state.Get("snapshot_id").(string)
|
||||||
|
bootscriptID := ""
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf("Creating image: %v", c.ImageName))
|
||||||
|
|
||||||
|
image, err := client.GetImage(c.Image)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error getting initial image info: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
if image.DefaultBootscript != nil {
|
||||||
|
bootscriptID = image.DefaultBootscript.Identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
imageID, err := client.PostImage(snapshotID, c.ImageName, bootscriptID, image.Arch)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error creating image: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Image ID: %s", imageID)
|
||||||
|
state.Put("image_id", imageID)
|
||||||
|
state.Put("image_name", c.ImageName)
|
||||||
|
state.Put("region", c.Region)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepImage) Cleanup(state multistep.StateBag) {
|
||||||
|
// no cleanup
|
||||||
|
}
|
@ -2,14 +2,15 @@ package scaleway
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type stepCreateServer struct {
|
type stepCreateServer struct {
|
||||||
serverId string
|
serverID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
@ -37,7 +38,7 @@ func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction {
|
|||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
s.serverId = server
|
s.serverID = server
|
||||||
|
|
||||||
state.Put("server_id", server)
|
state.Put("server_id", server)
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
|
func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
|
||||||
if s.serverId != "" {
|
if s.serverID != "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
|
|||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
ui.Say("Destroying server...")
|
ui.Say("Destroying server...")
|
||||||
err := client.PostServerAction(s.serverId, "terminate")
|
err := client.PostServerAction(s.serverID, "terminate")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Error(fmt.Sprintf(
|
ui.Error(fmt.Sprintf(
|
||||||
"Error destroying server. Please destroy it manually: %s", err))
|
"Error destroying server. Please destroy it manually: %s", err))
|
||||||
|
@ -13,11 +13,11 @@ type stepShutdown struct{}
|
|||||||
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
client := state.Get("client").(*api.ScalewayAPI)
|
client := state.Get("client").(*api.ScalewayAPI)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
serverId := state.Get("server_id").(string)
|
serverID := state.Get("server_id").(string)
|
||||||
|
|
||||||
ui.Say("Shutting down server...")
|
ui.Say("Shutting down server...")
|
||||||
|
|
||||||
err := client.PostServerAction(serverId, "poweroff")
|
err := client.PostServerAction(serverID, "poweroff")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error stopping server: %s", err)
|
err := fmt.Errorf("Error stopping server: %s", err)
|
||||||
@ -26,7 +26,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
|||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = api.WaitForServerState(client, serverId, "stopped")
|
_, err = api.WaitForServerState(client, serverID, "stopped")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error shutting down server: %s", err)
|
err := fmt.Errorf("Error shutting down server: %s", err)
|
||||||
|
@ -15,10 +15,10 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
|
|||||||
client := state.Get("client").(*api.ScalewayAPI)
|
client := state.Get("client").(*api.ScalewayAPI)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
c := state.Get("config").(Config)
|
c := state.Get("config").(Config)
|
||||||
volumeId := state.Get("root_volume_id").(string)
|
volumeID := state.Get("root_volume_id").(string)
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName))
|
ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName))
|
||||||
snapshot, err := client.PostSnapshot(volumeId, c.SnapshotName)
|
snapshot, err := client.PostSnapshot(volumeID, c.SnapshotName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
err := fmt.Errorf("Error creating snapshot: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
@ -26,15 +26,6 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
|
|||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Looking up snapshot ID for snapshot: %s", c.SnapshotName)
|
|
||||||
_, err = client.GetSnapshot(snapshot)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error looking up snapshot ID: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Snapshot ID: %s", snapshot)
|
log.Printf("Snapshot ID: %s", snapshot)
|
||||||
state.Put("snapshot_id", snapshot)
|
state.Put("snapshot_id", snapshot)
|
||||||
state.Put("snapshot_name", c.SnapshotName)
|
state.Put("snapshot_name", c.SnapshotName)
|
||||||
|
@ -13,11 +13,11 @@ type stepTerminate struct{}
|
|||||||
func (s *stepTerminate) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *stepTerminate) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
client := state.Get("client").(*api.ScalewayAPI)
|
client := state.Get("client").(*api.ScalewayAPI)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
serverId := state.Get("server_id").(string)
|
serverID := state.Get("server_id").(string)
|
||||||
|
|
||||||
ui.Say("Terminating server...")
|
ui.Say("Terminating server...")
|
||||||
|
|
||||||
err := client.DeleteServerForce(serverId)
|
err := client.DeleteServerForce(serverID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error terminating server: %s", err)
|
err := fmt.Errorf("Error terminating server: %s", err)
|
||||||
|
@ -3,7 +3,7 @@ layout: docs
|
|||||||
sidebar_current: docs-builders-scaleway
|
sidebar_current: docs-builders-scaleway
|
||||||
page_title: Scaleway - Builders
|
page_title: Scaleway - Builders
|
||||||
description: |-
|
description: |-
|
||||||
The Scaleway Packer builder is able to create new snapshots for use with
|
The Scaleway Packer builder is able to create new images for use with
|
||||||
Scaleway BareMetal and Virtual cloud server. The builder takes a source image, runs any provisioning
|
Scaleway BareMetal and Virtual cloud server. The builder takes a source image, runs any provisioning
|
||||||
necessary on the image after launching it, then snapshots it into a reusable
|
necessary on the image after launching it, then snapshots it into a reusable
|
||||||
image. This reusable image can then be used as the foundation of new servers
|
image. This reusable image can then be used as the foundation of new servers
|
||||||
@ -15,7 +15,7 @@ description: |-
|
|||||||
|
|
||||||
Type: `scaleway`
|
Type: `scaleway`
|
||||||
|
|
||||||
The `scaleway` Packer builder is able to create new snapshots for use with
|
The `scaleway` Packer builder is able to create new images for use with
|
||||||
[Scaleway](https://www.scaleway.com). The builder takes a source image,
|
[Scaleway](https://www.scaleway.com). The builder takes a source image,
|
||||||
runs any provisioning necessary on the image after launching it, then snapshots
|
runs any provisioning necessary on the image after launching it, then snapshots
|
||||||
it into a reusable image. This reusable image can then be used as the foundation
|
it into a reusable image. This reusable image can then be used as the foundation
|
||||||
@ -36,13 +36,15 @@ builder.
|
|||||||
|
|
||||||
### Required:
|
### Required:
|
||||||
|
|
||||||
- `api_organization` (string) - The organization ID to use to access your account.
|
- `api_access_key` (string) - The api_access_key to use to access your account.
|
||||||
It can also be specified via
|
It can also be specified via
|
||||||
environment variable `SCALEWAY_API_ORGANIZATION`.
|
environment variable `SCALEWAY_API_ACCESS_KEY`.
|
||||||
|
Your access key is available in the ["Credentials" section](https://cloud.scaleway.com/#/credentials) of the control panel.
|
||||||
|
|
||||||
- `api_token` (string) - The organization TOKEN to use to access your account.
|
- `api_token` (string) - The organization TOKEN to use to access your account.
|
||||||
It can also be specified via
|
It can also be specified via
|
||||||
environment variable `SCALEWAY_API_TOKEN`.
|
environment variable `SCALEWAY_API_TOKEN`.
|
||||||
|
Your tokens are available in the ["Credentials" section](https://cloud.scaleway.com/#/credentials) of the control panel.
|
||||||
|
|
||||||
- `image` (string) - The UUID of the base image to use. This is the
|
- `image` (string) - The UUID of the base image to use. This is the
|
||||||
image that will be used to launch a new server and provision it. See
|
image that will be used to launch a new server and provision it. See
|
||||||
@ -60,6 +62,9 @@ builder.
|
|||||||
|
|
||||||
- `server_name` (string) - The name assigned to the server.
|
- `server_name` (string) - The name assigned to the server.
|
||||||
|
|
||||||
|
- `image_name` (string) - The name of the resulting image that will
|
||||||
|
appear in your account.
|
||||||
|
|
||||||
- `snapshot_name` (string) - The name of the resulting snapshot that will
|
- `snapshot_name` (string) - The name of the resulting snapshot that will
|
||||||
appear in your account.
|
appear in your account.
|
||||||
|
|
||||||
@ -71,7 +76,7 @@ access tokens:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "scaleway",
|
"type": "scaleway",
|
||||||
"api_organization": "YOUR ORGANIZATION KEY",
|
"api_access_key": "YOUR API ACCESS KEY",
|
||||||
"api_token": "YOUR TOKEN",
|
"api_token": "YOUR TOKEN",
|
||||||
"image": "f01f8a48-c026-48ac-9771-a70eaac0890e",
|
"image": "f01f8a48-c026-48ac-9771-a70eaac0890e",
|
||||||
"region": "par1",
|
"region": "par1",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user