Part 1: repo Terraform
Satu folder per lini produk, masing-masing root module Terraform independen. Provider-nya di-pin ke0.1.0 dan required_version-nya >= 1.0 di tiap folder.
1.1 Anatomi lima file
Tiap folder isinya lima file yang sama. Semantiknya, persis:versions.tf
required_versionitu versi minimum Terraform CLI. Provider-nya sendiri pake Terraform Plugin Framework.required_providersnge-maps nama lokalbiznetgioke alamat registry dan nge-pin versinya.terraform initdownload persis0.1.0dari Terraform Registry.- Block
provider "biznetgio"yang kosong nge-configure provider dengan nol inline arguments. Provider-nya baca sendiri environment variableBIZNETGIO_API_KEY, makanya gak pernah ada token yang muncul di file.
variables.tf
Ngedeklarasiin semua input dengan types dan defaults. Tiga variable yang berulang di semua folder:
ssh_and_console_user: string, default"adminuser"(atau"root"digpu/). Komentarnya nyatet aturan API: 6 sampai 32 karakter, cuma huruf, angka, dash, dan dot.console_password: string,sensitive = true, sengaja gak ada default. Satu-satunya cara masuk cumaTF_VAR_console_passwordatauterraform.tfvarsyang di-gitignore. Variable ini ada dineolite/,neolite-pro/,gpu/, dancomplete/;baremetal/danobject-storage/autentikasi cuma pake keypair dan gak punya variable kayak gini.pay_with_credit_card: bool, defaultfalse.falsetetep bikin resource beneran, cuma invoice-nya dibiarin gak kebayar di portal. Liat Billing dan order.
terraform.tfvars.example
Template nilai asli yang di-commit. .gitignore nge-ignore *.tfvars tapi nge-include ulang *.tfvars.example, jadi template-nya ke-commit dan salinan yang udah diisi gak pernah. File contohnya cuma berisi password placeholder.
main.tf
Deklarasinya sendiri. Ada dua jenis deklarasi:
- Block
dataitu lookup read-only.data "biznetgio_neolite_products" "all" {}nanya ke API buat catalog terkini; hasilnya di-referensiin sebagaidata.biznetgio_neolite_products.all.products[0].product_id. - Block
resourcebikin dan ngelola sesuatu. Referensi antar mereka, kayakkeypair_id = biznetgio_neolite_keypair.main.keypair_id, yang Terraform ubah jadi dependency graph dan urutan API call. - Block
localsngitung nilai sekali, kayak milihproducts[0]buat demo.
outputs.tf
Nge-publish nilai setelah apply. Outputs sensitif (keypair_private_key, secret_key, console_url, openvpn_config) ditandai sensitive = true biar dirender masked di console dan di output JSON.
1.2 neolite/: cerita NEO Lite lengkap
Lima resources dan lima data sources, dirangkai dalam urutan yang bakal lu pake beneran:
Rantai dependency-nya kebaca sebagai: products → os_list → keypair → vm → disk, snapshot → restore. Referensi
snapshot_id inilah yang ngejamin snapshot udah ada sebelum restore diorder.
Opsi VM yang di-komen, tiap satu kepicu cuma pas nilainya berubah: power_state (start/stop/suspend/resume/shutdown), rebuild_os (ngilangin isi disk dan install ulang), migrate_to_pro (pindah satu arah ke product id NEO Lite Pro), disk_size (target absolut grow-only).
Outputs: ip_available, vm_status, vm_id (didokumentasiin sebagai nilai buat terraform import), keypair_private_key (sensitive), restored_vm_status, change_package_options_raw (sensitive), storage_upgrade_options_raw (sensitive).
1.3 neolite-pro/: bentuk sama, tier dedicated
Empat resources dan lima data sources. Wiring identik sama NEO Lite dengan perbedaan persis ini:- Gak ada
biznetgio_neolite_pro_vm_from_snapshot. Snapshot Pro sekarang cuma bisa di-restore lewat portal; banner file-nya bilang gitu. biznetgio_neolite_pro_disk.extraminimalnya 30 GB, bukan 60 GB,service_name = "pro-extra-disk",product_id = 60sama.vm_name = "pro-example"(nama yang lebih pendek juga muat di limit 6 sampai 16 karakter).- Migrasi ke Pro datang dari sisi Lite lewat
migrate_to_prodi contohneolite/. - Outputs-nya set yang sama minus
restored_vm_status.
variables.tf pake ulang tiga variable yang sama dan bilang gitu di komentar, nunjuk ke neolite/variables.tf buat penjelasan lengkap.
1.4 baremetal/: hardware dedicated
Lima resources dan tiga data sources, autentikasi keypair doang (gak ada console password di mana pun di folder;variables.tf cuma punya pay_with_credit_card):
Opsi server yang di-komen:
power_state (on/off), reset_trigger (reboot one-shot, ganti string-nya buat ke-fire lagi), rebuild_os (ngilangin isi disk; nilai valid dari data source rebuild OS list di atas).
Outputs: server_status, server_ip_address, keypair_private_key (sensitive), valid_rebuild_os_images, additional_ip_address, elastic_storage_status, openvpn_config (sensitive).
1.5 gpu/: dua keanehan, secara konkret
Dua resources dan tiga data sources.variables.tf nge-set ssh_and_console_user ke "root" (image GPU-nya pake root).
biznetgio_gpu_keypairgak nge-export atributkeypair_id. Dia nge-exportid(yang emang udah jadi keypair id-nya),public_key, danprivate_key. Instance-nya nge-referensi.id.subscriptiondanon_demanditu atribut bertipe object, bukan block. Mereka di-assign dengan=dan persis satu dari dua yang harus di-set; Terraform nolak plan yang gak ada dua-duanya atau ada dua-duanya. Bentukon_demandyang di-komen ituon_demand = { additional_hours = 0 }dan nge-tag per jam. Dua trigger one-shot di-komen:rebuild_trigger(ngilangin isi disk) danreserve_additional_hours_trigger.
data.biznetgio_gpu_console.console itu side-effecting (tiap read bikin session console one-time yang baru; aman di terraform output, jangan pernah nge-referensi dari apapun yang di-evaluasi pas plan diffing) dan data.biznetgio_gpu_graph.graph dengan timeframe = "hour".
Outputs: gpu_status, keypair_private_key (sensitive), console_url (sensitive), monitoring_graph.
1.6 object-storage/: storage S3-compatible
Empat resources dan tiga data sources, gak ada keypair dan gak ada password;variables.tf cuma punya pay_with_credit_card:
Komentar di resource upload nge-set batasannya: upload lewat control-plane itu oke buat satu file kecil; yang lebih gede atau massal harus pake tooling S3 beneran (aws-cli, rclone) dengan credential-nya, yang nunjuk ke
nos.<region>.neo.id.
Outputs: storage_status, bucket_name, access_key (sensitive), secret_key (sensitive), active_instances (sensitive karena tiap item bawa field JSON raw yang di-redact, dan Terraform nganggep apapun yang berisi nilai sensitif sebagai sensitif seutuhnya).
1.7 complete/: modulnya
main.tf di root ngelakuin catalog lookups terus satu module call dengan sepuluh arguments; variables.tf di root cuma nerusin console_password dan pay_with_credit_card; outputs.tf di root nge-pass through semua output module.
Di dalam modules/app-stack/:
main.tfngedeklarasiin lima resources:biznetgio_neolite_pro_keypair.this(dinamain"${var.name}-key"),biznetgio_neolite_pro_vm.this(vm_name = var.name),biznetgio_object_storage.this,biznetgio_object_storage_bucket.this("${var.name}-assets",acl = "private"), danbiznetgio_object_storage_credential.this. Semua referensi pake resource module-nya sendiri, jadi module-nya fully self-contained.variables.tfngedeklarasiin sepuluh inputs:name(nama VM dan prefix buat bucket dan credential, 6 sampai 16 karakter),storage_label(dipisah sengaja karena storage label punya limit 6 sampai 16 karakter sendiri),vm_product_id,select_os,storage_product_id,cycle(default"m"),ssh_and_console_user(default"adminuser"),console_password(sensitive, gak ada default),pay_with_credit_card(defaultfalse),storage_quota(default10).outputs.tfnge-exportvm_status,bucket_name,access_key(sensitive),secret_key(sensitive),keypair_private_key(sensitive). Komentar di yang sensitive bilang caller module-nya yang pegang tanggung jawab buat nyimpen.- Block
terraform { required_providers { biznetgio = { source = "registry.terraform.io/shirasakaren/biznetgio" } } }miliknya sendiri. Tanpa itu, Terraform nganggep namespace defaulthashicorp/*buat provider yang di-referensiin di dalam child module dan gagal dengan error missing provider. Module apa pun yang di-copy keluar dari repo ini harus nahan block itu.
1.8 Workflow-nya
.github/workflows/ci.yml ngejalanin terraform fmt -check, terraform init, dan terraform validate tanpa syarat per folder yang dipilih, terus secara kondisional plan, apply, atau destroy dengan secret BIZNETGIO_API_KEY dan TF_VAR_console_password. Penjelasan baris per baris lengkapnya ada di Pipelines.
Part 2: repo Pulumi
Enam contoh, tiap satu ditulis di enam bahasa. Tiap folder itu Pulumi project independen denganPulumi.yaml-nya sendiri.
2.1 Anatomi project per bahasa
Pulumi.yaml di tiap folder:
name unik per folder, runtime milih engine bahasa (nodejs, python, go, dotnet, java, atau yaml). Di sekitarnya, tiap bahasa nyumbang:
Nama file csproj-nya nyocokin
name di Pulumi.yaml (contohnya biznetgio-example-gpu.csproj). .gitignore nge-exclude node_modules/, __pycache__/, venv/, bin/, obj/, target/, *.class, dan Pulumi.*.yaml dengan !Pulumi.yaml yang di-include ulang, jadi stack state dan secret gak masuk git.
2.2 Model program yang dipake bareng
Tiap program ngelakuin lima hal yang sama, dalam urutan yang sama:- Config. Baca pengaturan stack.
consolePassworditu required dan secret (config.requireSecret("consolePassword"),cfg.RequireSecret(...),config.require_secret(...));payWithCreditCarditu boolean opsional yang default-nya false. Set mereka denganpulumi config set --secret consolePassword <value>. - Catalog lookups. Invoke catalog produk, terus OS list buat produk pertama, kadang IP availability. Function-nya itu API call read-only.
- Resources. Bikin keypair, VM, dan semuanya. Ngelewatin output satu resource ke input resource lain itu yang ngebangun dependency graph.
- Outputs. Export nilai yang lu peduliin.
- Run. Tiap bahasa punya mekanisme entry sendiri: TypeScript dan Python jalan di top-level; Go nge-bungkus semuanya di
pulumi.Run(func(ctx *pulumi.Context) error {...})dengan return(value, error)di tiap call; .NET pake top-level statements yang nge-returnawait Deployment.RunAsync(() => {...})dan export lewatDictionary<string, object?>yang di-return; Java nge-bungkus diPulumi.run(App::stack)dengan callctx.export(...); YAML ngedeklarasiin sectionconfiguration,variables,resources, danoutputsalih-alih kode.
2.3 Konversi account id
id resource di Pulumi selalu string, tapi API upstream-nya kirim number, jadi provider nge-typed beberapa input model “account id” sebagai number. Ngelewatin id string ke mereka itu compile error di semua bahasa typed, makanya tiap bahasa bawa helper konversi. Implementasinya yang persis:
- TypeScript
- Python
- Go
- .NET
- Java
- YAML
Di mana konversinya gak berlaku, sama pentingnya:
keypairIddi VM VPS dan baremetal itu di-typed string:NeoliteKeypair,NeoliteProKeypair, danBaremetalKeypairsemuanya nge-exposekeypairIdstring yang nge-pass straight through.- Semua
accountIdObject Storage (ObjectStorageBucket,ObjectStorageCredential,ObjectStorageObject, dan tiga catalog function) di-typed string. Gak ada konversi di mana pun diobject-storage/ataucomplete/. GpuConsoledanGpuGraphnerimaaccountIdsebagai string; contoh GPU ngelewatingpu.idlangsung.GpuKeypairgak nge-exposekeypairIdsama sekali, cumaid,publicKey, danprivateKey. Contoh GPU ngonversikeypair.iddengan helper yang sama (inline, sebagaikeypair.id.apply((id) => Number(id))di TypeScript,keypair_id = keypair.id.apply(int)di Python, dan padanannya di bahasa lain) dan nge-komen kenapa.
object-storage/: ngejalanin kode biasa kayak .length terhadap nilai akhir sebuah Output butuh .apply (TypeScript), .apply(len) (Python), ApplyT (Go), .Apply (.NET), .applyValue (Java), atau fn::length (YAML). Contoh-contohnya pake itu buat nge-export jumlah active instances, buckets, dan credentials.
2.4 Contoh-contohnya, inventaris per lini produk
neolite/: products → osList → ipAvailability → keypair → vm (dengan empat opsi yang di-komenpowerState, rebuildOs, migrateToPro, diskSize) → disk (minimal 60 GB, productId: 60) → snapshot → VM restored dari snapshot → dua raw JSON function. Exports: ipAvailable, vmStatus, vmId, keypairPrivateKey, diskStatus, restoredVmStatus, changePackageOptionsRaw, storageUpgradeOptionsRaw.
neolite-pro/: sama minus resource dari snapshot, dengan minimal disk 30 GB dan serviceName: "pro-extra-disk". Exports nambah diskStatus dan snapshotStatus, buang restoredVmStatus.
baremetal/: products → keypair → server (publicIp: 1, powerState yang di-komen, resetTrigger, rebuildOs) → rebuild OS list (accountId yang dikonversi) → OpenVPN (gak ada inputs) → additional IP (productId: 10, region: "wjv-1") → assignment (dua id-nya dikonversi) → elastic storage (productId: 20, size: 100, metalAccountId yang dikonversi). Exports: keypairPrivateKey, serverStatus, serverIpAddress, validRebuildOsImages, openvpnConfig, additionalIpAddress, assignmentStatus, elasticStorageStatus.
gpu/: products → keypair → instance. Mode billing-nya satu properti: subscription: { cycle: "m" } (object literals TypeScript/Python), subscription=biznetgio.GpuSubscriptionArgsArgs(cycle="m") (SDK Python nge-namain kelas args dengan ArgsArgs ganda), Subscription: biznetgio.GpuSubscriptionArgsArgs{Cycle: pulumi.String("m")} (Go), Subscription = new GpuInstanceSubscriptionArgs { Cycle = "m" } (.NET), .subscription(GpuSubscriptionArgs.builder().cycle("m").build()) (Java, di-import dari ren.shirasaka.biznetgio.inputs), dan subscription: { cycle: m } (YAML). Alternatif onDemand (dengan additionalHours) di-komen di semua bahasa, sama kayak rebuildTrigger dan reserveAdditionalHoursTrigger. Terus function console yang side-effecting dan graph-nya (timeframe: "hour"), dua-duanya dengan accountId string. Exports: keypairPrivateKey, gpuStatus, consoleUrl, monitoringGraph.
object-storage/: subscription (productId: 8, quota: 10) → bucket (acl: "public-read") → credential (active: true) → upload object → tiga catalog lookups dengan hitungan. Path source beda per bahasa: path.join(__dirname, "index.html") (TypeScript), str(pathlib.Path(__file__).parent / "index.html") (Python), "index.html" relatif (Go dan Java, di mana pulumi up jalan dari folder project), Path.Combine(AppContext.BaseDirectory, "index.html") (.NET), dan source: ./index.html (YAML). Exports: storageStatus, bucketName, accessKey, secretKey, objectKey, activeInstanceCount, bucketCount, credentialCount.
2.5 Contoh complete/ dan component-nya
Foldercomplete/ nge-bungkus VM NEO Lite Pro plus Object Storage jadi component AppStack yang reusable. Caller-nya (index.ts, __main__.py, main.go, Program.cs, App.java) ngelakuin catalog lookups, ngebangun satu AppStack dengan args, dan nge-export ulang lima output-nya: vmStatus, bucketName, accessKey, secretKey, keypairPrivateKey.
Internal component-nya identik di semua bahasa:
- Registrasi dengan type string
"biznetgio-example:index:AppStack". - Lima children: keypair, VM, storage, bucket, credential, tiap satu dinamain
${name}-key,${name}-vm,${name}-storage,${name}-assets,${name}-cred. - Tiap child dapet component-nya sebagai parent:
{ parent: this }(TypeScript),opts=pulumi.ResourceOptions(parent=self)(Python),pulumi.Parent(app)(Go),new CustomResourceOptions { Parent = this }(.NET),CustomResourceOptions.builder().parent(this).build()(Java). Wiring parent nge-group semuanya di satu tree biarpulumi destroyngehancurin seluruh app dalam urutan yang bener. - Outputs di-publish dengan
registerOutputs({...})(TypeScript),self.register_outputs({...})(Python),ctx.RegisterResourceOutputs(app, pulumi.Map{...})(Go),RegisterOutputs(new Dictionary<string, object?>{...})(.NET),this.registerOutputs(Map.of(...))(Java). - Args: TypeScript pake interface
AppStackArgs; Python kelas biasa denganstorage_quotayang default-nyaNonedan component-nya fallback ke 10; Go struct denganStorageQuota *intdi mana nil artinya default 10 GB; .NET kelas denganInput<int>? StorageQuotayang nullable danargs.StorageQuota ?? 10; Java kelas builder (AppStackArgs.java) di manastorageQuotadefault-nyaInput.of(10);aclbucket-nyaprivatedanvmNameVM-nya nama component-nya sendiri.
yaml/complete/Pulumi.yaml ngedeklarasiin lima resource yang sama secara flat dengan komentar yang ngejelasin bedanya.
2.6 Workflow-nya
.github/workflows/ci.yml nge-build tiap bahasa dengan toolchain-nya, terus secara opsional ngejalanin preview/up/destroy lewat pulumi/actions@v7 lawan stack dev. Detail lengkap di Pipelines.
Part 3: referensi teknis lintas repo
Model billing
Tiap resource yang bisa diorder nerimacycle ("m" bulanan, "y" tahunan) dan payWithCreditCard. Di Pulumi field yang sama itu cycle dan payWithCreditCard; aturan billing default-false berlaku di dua tool. GPU nambahin pemisahan subscription lawan onDemand, dan additional_hours nge-reserve jam on-demand tambahan di atas saldo default.
Opsi one-shot dan destruktif
Semua di-komen di tiap bahasa, semua cuma kepicu pas nilainya berubah:Read yang side-effecting
Cumabiznetgio_gpu_console / gpuConsole yang nge-mint state pas di-read: tiap evaluasi bikin session console one-time yang fresh. Contoh-contohnya nge-expose itu sebagai output dan nge-warning buat jangan pernah nge-referensi dari apapun yang di-evaluasi pas preview diffing. Raw JSON lookups (changePackageOptions, storageUpgradeOptions) itu read-only.
Limit nama yang dipaksain API
vm_name/vmName: 6 sampai 16 karakter. service_name/serviceName (disk): 6 sampai 16. label Object Storage: 6 sampai 16. ssh_and_console_user: 6 sampai 32, cuma huruf, angka, dash, dan dot. Nama-nama contohnya (neolite-example, extra-disk, pro-extra-disk, gpu-example, metal-example, example) semuanya dipilih biar muat.
Secret per tool
Terraform:console_password lewat TF_VAR_console_password atau terraform.tfvars yang di-gitignore; auth provider lewat env BIZNETGIO_API_KEY. Pulumi: pulumi config set --secret consolePassword; stack state di Pulumi.<stack>.yaml yang di-gitignore; auth provider lewat env BIZNETGIO_API_KEY; CI nambahin PULUMI_ACCESS_TOKEN.
Part 4: file index lengkap
Repo Terraform, semua file-nya:
Repo Pulumi, semua file-nya:
Itu semua yang ada di kedua repo. Kalau lu ngubah salah satunya, Konvensi dan walkthrough ngejelasin cara ngubahnya dengan aman.