Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/apis/app/v1alpha1/catalog_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ type VMCatalog struct {
Network string `json:"network"`
// +optional
Capacity Capacity `json:"capacity"`
// +optional
// +kubebuilder:default=linux
// +kubebuilder:validation:Enum=linux;ibmi;aix
OS string `json:"os"`
}

//+kubebuilder:object:root=true
Expand Down
8 changes: 8 additions & 0 deletions api/config/crd/bases/app.pac.io_catalogs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ spec:
description:
type: string
expiry:
default: 5
type: integer
image_thumbnail_reference:
description: Thumbnail reference for image in Catalog which consists
Expand Down Expand Up @@ -81,6 +82,13 @@ spec:
type: string
network:
type: string
os:
default: linux
enum:
- linux
- ibmi
- aix
type: string
processor_type:
type: string
system_type:
Expand Down
45 changes: 45 additions & 0 deletions api/controllers/app/service/templates/userdata.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#cloud-config
runcmd:
- |
{
set -x
echo "--- STARTING CLOUD-INIT DEPLOYMENT ---"

# 1. Create User Profile
/QOpenSys/usr/bin/system "CRTUSRPRF USRPRF({{.Username}}) PASSWORD(*NONE) USRCLS(*SECOFR) SPCAUT(*ALLOBJ *SECADM *JOBCTL) STATUS(*ENABLED) HOMEDIR('/home/{{.Username}}') GID(*GEN) UID(*GEN)"

sleep 5

# 2. Disable Global Sign-on Lock
/QOpenSys/usr/bin/system "CHGSYSVAL SYSVAL(QRMTSIGN) VALUE('*VERIFY')"

# 3. Create SSH Directory
mkdir -p /home/{{.Username}}/.ssh

# 4. Inject SSH Key(s) and FORCE CCSID 1208 (Critical for IBM i)
printf "{{.Keys}}\n" > /home/{{.Username}}/.ssh/authorized_keys
/QOpenSys/usr/bin/system "CHGATR OBJ('/home/{{.Username}}/.ssh/authorized_keys') ATR(*CCSID) VALUE(1208)"

# 5. Change Ownership
/QOpenSys/usr/bin/system "CHGOWN OBJ('/home/{{.Username}}') NEWOWN({{.Username}}) SUBTREE(*ALL)"

# 6. Secure Permissions
chmod 700 /home/{{.Username}}
chmod 700 /home/{{.Username}}/.ssh
chmod 600 /home/{{.Username}}/.ssh/authorized_keys

# 7. SSHD Config Update
CONF="/QOpenSys/QIBM/UserData/SC1/OpenSSH/etc/sshd_config"
if [ -f "$CONF" ]; then
sed 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/' "$CONF" > /tmp/sshd.tmp && mv /tmp/sshd.tmp "$CONF"
sed 's|^#\?AuthorizedKeysFile.*|AuthorizedKeysFile .ssh/authorized_keys|' "$CONF" > /tmp/sshd.tmp && mv /tmp/sshd.tmp "$CONF"
chmod 644 "$CONF"
fi

# 8. Restart SSH
/QOpenSys/usr/bin/system "ENDTCPSVR *SSHD"
sleep 5
/QOpenSys/usr/bin/system "STRTCPSVR *SSHD"

echo "--- DEPLOYMENT FINISHED ---"
} > /tmp/cloud-init-trace.log 2>&1
46 changes: 45 additions & 1 deletion api/controllers/app/service/vm.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package service

import (
"bytes"
"context"
_ "embed"
"encoding/base64"
"fmt"
"strconv"
"strings"
"text/template"

"github.com/IBM-Cloud/power-go-client/power/models"
"github.com/IBM/go-sdk-core/v5/core"
Expand All @@ -19,9 +22,14 @@ const (
publicNetworkPrefix = "pac-public-network"
)

//go:embed templates/userdata.yaml
var userDataTemplate string

var (
ErroNoPublicNetwork = errors.New("no public network available to use for vm creation")
dnsServers = []string{"9.9.9.9", "1.1.1.1"}
IbmiUsername = "PAC"
IbmiOS = "ibmi"
)

func getAvailablePubNetwork(scope *scope.ServiceScope) (string, error) {
Expand Down Expand Up @@ -176,6 +184,7 @@ func createVM(scope *scope.ServiceScope) error {

memory := float64(vmSpec.Capacity.Memory)
processors, _ := strconv.ParseFloat(vmSpec.Capacity.CPU, 64)
userData := getUserData(vmSpec, scope)
createOpts := &models.PVMInstanceCreate{
ServerName: &scope.Service.Name,
ImageID: imageRef.ImageID,
Expand All @@ -184,7 +193,7 @@ func createVM(scope *scope.ServiceScope) error {
Processors: &processors,
SysType: vmSpec.SystemType,
ProcType: &vmSpec.ProcessorType,
UserData: base64.StdEncoding.EncodeToString([]byte(strings.Join(scope.Service.Spec.SSHKeys, "\n"))),
UserData: userData,
}

pvmInstanceList, err := scope.PowerVSClient.CreateVM(createOpts)
Expand All @@ -199,3 +208,38 @@ func createVM(scope *scope.ServiceScope) error {
scope.Service.Status.VM.InstanceID = *(*pvmInstanceList)[0].PvmInstanceID
return nil
}

func getUserData(vmSpec appv1alpha1.VMCatalog, scope *scope.ServiceScope) string {
var userData string
switch vmSpec.OS {
case IbmiOS:
userData = getIBMiUserData(scope.Service.Spec.SSHKeys, IbmiUsername, scope)
default:
userData = base64.StdEncoding.EncodeToString(
[]byte(strings.Join(scope.Service.Spec.SSHKeys, "\n")),
)
}
return userData
}

func getIBMiUserData(sshKeys []string, username string, scope *scope.ServiceScope) string {
allKeys := strings.Join(sshKeys, "\n")
cloudInitTemplate, err := template.New("userdata").Parse(userDataTemplate)
if err != nil {
scope.Logger.Error(err, "error parsing cloud-init template")
return ""
}
var buf bytes.Buffer
err = cloudInitTemplate.Execute(&buf, struct {
Username string
Keys string
}{
Username: username,
Keys: allKeys,
})
if err != nil {
scope.Logger.Error(err, "error executing cloud-init template")
return ""
}
return base64.StdEncoding.EncodeToString(buf.Bytes())
}
Loading