From 3057325a87eece6541bfc818e69d6183620b4cde Mon Sep 17 00:00:00 2001 From: Blagoy Simandoff Date: Fri, 13 Sep 2024 10:54:52 +0300 Subject: [PATCH 1/3] Added methods for ILX Workspaces --- cmd/ilx_example/example.go | 61 ++++++++++++++ cmd/ilx_example/ilx/index.js | 26 ++++++ cmd/ilx_example/ilx/package-lock.json | 34 ++++++++ cmd/ilx_example/ilx/package.json | 15 ++++ device.go | 28 ++++--- ilx.go | 110 ++++++++++++++++++++++++++ sys.go | 34 +++++++- 7 files changed, 292 insertions(+), 16 deletions(-) create mode 100644 cmd/ilx_example/example.go create mode 100644 cmd/ilx_example/ilx/index.js create mode 100644 cmd/ilx_example/ilx/package-lock.json create mode 100644 cmd/ilx_example/ilx/package.json create mode 100644 ilx.go diff --git a/cmd/ilx_example/example.go b/cmd/ilx_example/example.go new file mode 100644 index 0000000..f3b3c28 --- /dev/null +++ b/cmd/ilx_example/example.go @@ -0,0 +1,61 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/f5devcentral/go-bigip" +) + +func main() { + // Connect to the BIG-IP system. + config := bigip.Config{ + Address: os.Getenv("BIG_IP_HOST"), + Username: os.Getenv("BIG_IP_USER"), + Password: os.Getenv("BIG_IP_PASSWORD"), + CertVerifyDisable: true, + } + + f5 := bigip.NewSession(&config) + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + const wrkspcName = "ExampleWorkspce" + err := f5.CreateWorkspace(ctx, wrkspcName) + if err != nil { + panic(err) + } + result, err := f5.GetWorkspace(ctx, wrkspcName) + if err != nil { + panic(err) + } + log.Printf("Workspace: %v", result) + opts := bigip.ExtensionConfig{ + WorkspaceName: wrkspcName, + Name: "exampleExt", + Partition: "Common", + } + err = f5.CreateExtension(ctx, opts) + if err != nil { + panic(err) + } + + err = f5.WriteExtensionFile(ctx, opts, "{}", bigip.PackageJSON) + if err != nil { + panic(err) + } + + err = f5.WriteExtensionFile(ctx, opts, "const a = 12;", bigip.IndexJS) + if err != nil { + panic(err) + } + + content, err := f5.ReadExtensionFile(ctx, opts, bigip.IndexJS) + if err != nil { + panic(err) + } + fmt.Printf("Content: %+v\n", content) +} diff --git a/cmd/ilx_example/ilx/index.js b/cmd/ilx_example/ilx/index.js new file mode 100644 index 0000000..be4a0bb --- /dev/null +++ b/cmd/ilx_example/ilx/index.js @@ -0,0 +1,26 @@ +var f5 = require("f5-nodejs"); + +var ilx = new f5.ILXServer(); + +ilx.addMethod("getCredentials", function (req, res) { + const [user] = req.params(); + const arc = new CyberArc(); + const credentials = arc.getCredentials(user); + res.reply(credentials); +}); + +ilx.listen(); + +/** + * @class CyberArc + */ +class CyberArc { + /** + * @method getCredentials + * @param {string} user + * @returns {Array} [username, password] + */ + getCredentials(username) { + return ["admin", "password"]; + } +} diff --git a/cmd/ilx_example/ilx/package-lock.json b/cmd/ilx_example/ilx/package-lock.json new file mode 100644 index 0000000..173153a --- /dev/null +++ b/cmd/ilx_example/ilx/package-lock.json @@ -0,0 +1,34 @@ +{ + "name": "ilx", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ilx", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "f5-nodejs": "^1.0.0", + "hexy": "^0.3.5" + } + }, + "node_modules/f5-nodejs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/f5-nodejs/-/f5-nodejs-1.0.0.tgz", + "integrity": "sha512-NUTwNOKVMqyk65ba4xkabJx77FvEZRJt3gpQjtNOfvEFxKKAHFAhfcgCY8xiP8fL3Iua4so1EJKsMxhVJk5OUg==" + }, + "node_modules/hexy": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/hexy/-/hexy-0.3.5.tgz", + "integrity": "sha512-UCP7TIZPXz5kxYJnNOym+9xaenxCLor/JyhKieo8y8/bJWunGh9xbhy3YrgYJUQ87WwfXGm05X330DszOfINZw==", + "license": "MIT", + "bin": { + "hexy": "bin/hexy_cmd.js" + }, + "engines": { + "node": ">=10.4" + } + } + } +} diff --git a/cmd/ilx_example/ilx/package.json b/cmd/ilx_example/ilx/package.json new file mode 100644 index 0000000..8b8a168 --- /dev/null +++ b/cmd/ilx_example/ilx/package.json @@ -0,0 +1,15 @@ +{ + "name": "ilx", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "npm run dev" + }, + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "f5-nodejs": "^1.0.0", + "hexy": "^0.3.5" + } +} diff --git a/device.go b/device.go index d712e13..09fa6dd 100644 --- a/device.go +++ b/device.go @@ -185,19 +185,21 @@ func (p *Devicegroup) UnmarshalJSON(b []byte) error { // https://10.192.74.80/mgmt/cm/device/licensing/pool/purchased-pool/licenses // The above command will spit out license uuid and which should be mapped uriUuid const ( - uriMgmt = "mgmt" - uriCm = "cm" - uriDiv = "device" - uriDevices = "devices" - uriDG = "device-group" - uriLins = "licensing" - uriPoo = "pool" - uriPur = "purchased-pool" - uriLicn = "licenses" - uriMemb = "members" - uriUtility = "utility" - uriOfferings = "offerings" - uriF5BIGMSPBT10G = "f37c66e0-a80d-43e8-924b-3bbe9fe96bbe" + uriMgmt = "mgmt" + uriCm = "cm" + uriDiv = "device" + uriDevices = "devices" + uriDG = "device-group" + uriLins = "licensing" + uriPoo = "pool" + uriPur = "purchased-pool" + uriLicn = "licenses" + uriMemb = "members" + uriUtility = "utility" + uriOfferings = "offerings" + uriF5BIGMSPBT10G = "f37c66e0-a80d-43e8-924b-3bbe9fe96bbe" + uriWorkspace = "workspace" + WORKSPACE_UPLOAD_PATH = "/var/ilx/workspaces" ) func (p *LIC) MarshalJSON() ([]byte, error) { diff --git a/ilx.go b/ilx.go new file mode 100644 index 0000000..de59cb8 --- /dev/null +++ b/ilx.go @@ -0,0 +1,110 @@ +package bigip + +import ( + "context" + "fmt" +) + +type ILXWorkspace struct { + Name string `json:"name,omitempty"` + FullPath string `json:"fullPath,omitempty"` + SelfLink string `json:"selfLink,omitempty"` + NodeVersion string `json:"nodeVersion,omitempty"` + StagedDirectory string `json:"stagedDirectory,omitempty"` + Version string `json:"version,omitempty"` + Extensions []Extension `json:"extensions,omitempty"` + Rules []ILXFile `json:"rules,omitempty"` + Generation int `json:"generation,omitempty"` +} + +type ILXFile struct { + Name string `json:"name,omitempty"` + Content string `json:"content,omitempty"` +} + +type Extension struct { + Name string `json:"name,omitempty"` + Files []ILXFile `json:"files,omitempty"` +} + +func (b *BigIP) GetWorkspace(ctx context.Context, path string) (*ILXWorkspace, error) { + spc := &ILXWorkspace{} + err, exists := b.getForEntity(spc, uriMgmt, uriTm, uriIlx, uriWorkspace, path) + if !exists { + return nil, fmt.Errorf("workspace does not exist: %w", err) + } + if err != nil { + return nil, fmt.Errorf("error getting ILX Workspace: %w", err) + } + + return spc, nil +} + +func (b *BigIP) CreateWorkspace(ctx context.Context, path string) error { + err := b.post(ILXWorkspace{Name: path}, uriMgmt, uriTm, uriIlx, uriWorkspace, "") + if err != nil { + return fmt.Errorf("error creating ILX Workspace: %w", err) + } + + return nil +} + +func (b *BigIP) DeleteWorkspace(ctx context.Context, name string) error { + err := b.delete(uriMgmt, uriTm, uriIlx, uriWorkspace, name) + if err != nil { + return fmt.Errorf("error deleting ILX Workspace: %w", err) + } + return nil +} + +type ExtensionConfig struct { + Name string `json:"name,omitempty"` + Partition string `json:"partition,omitempty"` + WorkspaceName string `json:"workspaceName,omitempty"` +} + +func (b *BigIP) CreateExtension(ctx context.Context, opts ExtensionConfig) error { + err := b.post(ILXWorkspace{Name: opts.WorkspaceName}, uriMgmt, uriTm, uriIlx, uriWorkspace+"?options=extension,"+opts.Name) + if err != nil { + return fmt.Errorf("error creating ILX Extension: %w", err) + } + return nil +} + +type ExtensionFile string + +func (e ExtensionFile) Validate() error { + if e != PackageJSON && e != IndexJS { + return fmt.Errorf("invalid extension file") + } + return nil +} + +const ( + PackageJSON ExtensionFile = "package.json" + IndexJS ExtensionFile = "index.js" +) + +func (b *BigIP) WriteExtensionFile(ctx context.Context, opts ExtensionConfig, content string, filename ExtensionFile) error { + if err := filename.Validate(); err != nil { + return err + } + destination := fmt.Sprintf("%s/%s/%s/extensions/%s/%s", WORKSPACE_UPLOAD_PATH, opts.Partition, opts.WorkspaceName, opts.Name, filename) + err := b.WriteFile(content, destination) + if err != nil { + return fmt.Errorf("error uploading packagejson: %w", err) + } + return nil +} + +func (b *BigIP) ReadExtensionFile(ctx context.Context, opts ExtensionConfig, filename ExtensionFile) (*ILXFile, error) { + if err := filename.Validate(); err != nil { + return nil, err + } + destination := fmt.Sprintf("%s/%s/%s/extensions/%s/%s", WORKSPACE_UPLOAD_PATH, opts.Partition, opts.WorkspaceName, opts.Name, filename) + files, err := b.ReadFile(destination) + if err != nil { + return nil, err + } + return files, nil +} diff --git a/sys.go b/sys.go index 5e39b23..33227ef 100644 --- a/sys.go +++ b/sys.go @@ -15,6 +15,7 @@ import ( "fmt" "log" "os" + "path" "strconv" //"strings" @@ -668,7 +669,6 @@ func (b *BigIP) CreateProvision(name string, fullPath string, cpuRatio int, disk } if name == "afm" { return b.put(config, uriSys, uriProvision, uriAfm) - } if name == "gtm" { return b.put(config, uriSys, uriProvision, uriGtm) @@ -834,7 +834,6 @@ func (b *BigIP) StartTransaction() (*Transaction, error) { b.Transaction = "" body := make(map[string]interface{}) resp, err := b.postReq(body, uriMgmt, uriTm, uriTransaction) - if err != nil { return nil, fmt.Errorf("error encountered while starting transaction: %v", err) } @@ -1051,7 +1050,6 @@ func (b *BigIP) GetOCSP(name string) (*OCSP, error) { } js, err := json.Marshal(ocsp) - if err != nil { return nil, fmt.Errorf("error encountered while marshalling ocsp: %v", err) } @@ -1098,3 +1096,33 @@ func (b *BigIP) ModifyFolderDescription(partition string, body map[string]string partition = fmt.Sprintf("~%s", partition) return b.patch(body, uriSys, uriFolder, partition) } + +// Write to a file using the echo command: echo "content" > destination +func (b *BigIP) WriteFile(content string, destination string) error { + cmd := BigipCommand{ + Command: "run", + UtilCmdArgs: fmt.Sprintf("-c 'echo \"%s\" > \"%s\"'", content, destination), + } + _, err := b.RunCommand(&cmd) + if err != nil { + return fmt.Errorf("error running command: %w", err) + } + return nil +} + +// Read a file using the cat command +func (b *BigIP) ReadFile(destination string) (*ILXFile, error) { + fileContentCommand := BigipCommand{ + Command: "run", + UtilCmdArgs: fmt.Sprintf("-c 'cat %s'", destination), + } + fileContent, err := b.RunCommand(&fileContentCommand) + if err != nil { + return nil, fmt.Errorf("error running command: %w", err) + } + file := &ILXFile{ + Name: path.Base(destination), + Content: fileContent.CommandResult, + } + return file, nil +} From 7bab9add6dda4ee7cae19e8c024e407d74d2a9bb Mon Sep 17 00:00:00 2001 From: Blagoy Simandoff Date: Fri, 13 Sep 2024 12:44:21 +0300 Subject: [PATCH 2/3] Added read and write methods for ILXRules and refactored the input params for read/write ilxExtension files --- cmd/ilx_example/example.go | 62 +++++++++++++++++++++++++++++++++----- ilx.go | 40 ++++++++++++++++++------ 2 files changed, 85 insertions(+), 17 deletions(-) diff --git a/cmd/ilx_example/example.go b/cmd/ilx_example/example.go index f3b3c28..2be9f2a 100644 --- a/cmd/ilx_example/example.go +++ b/cmd/ilx_example/example.go @@ -19,43 +19,89 @@ func main() { CertVerifyDisable: true, } + // Create a new BIG-IP session with the provided configuration. f5 := bigip.NewSession(&config) + // Create a context with a timeout of 1 second. ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() - const wrkspcName = "ExampleWorkspce" - err := f5.CreateWorkspace(ctx, wrkspcName) + + // Define the workspace configuration. + workspaceConfig := bigip.WorkspaceConfig{ + WorkspaceName: "ExampleWorkspace", + Partition: "Common", + } + + // Create a new workspace. + err := f5.CreateWorkspace(ctx, workspaceConfig.WorkspaceName) if err != nil { panic(err) } - result, err := f5.GetWorkspace(ctx, wrkspcName) + + // Fetch the details of the created workspace. + result, err := f5.GetWorkspace(ctx, workspaceConfig.WorkspaceName) if err != nil { panic(err) } log.Printf("Workspace: %v", result) + + // Define the extension configuration. opts := bigip.ExtensionConfig{ - WorkspaceName: wrkspcName, - Name: "exampleExt", - Partition: "Common", + WorkspaceConfig: workspaceConfig, + ExtensionName: "exampleExt", } + + // Create a new extension. err = f5.CreateExtension(ctx, opts) if err != nil { panic(err) } - err = f5.WriteExtensionFile(ctx, opts, "{}", bigip.PackageJSON) + // Read the package.json file. + const packagePath string = "cmd/ilx_example/ilx/package.json" + packagejson, err := os.ReadFile(packagePath) + if err != nil { + fmt.Println("File reading error", err) + return + } + + // Write the package.json file to the extension. + err = f5.WriteExtensionFile(ctx, opts, string(packagejson), bigip.PackageJSON) if err != nil { panic(err) } - err = f5.WriteExtensionFile(ctx, opts, "const a = 12;", bigip.IndexJS) + // Read the index.js file. + const indexjsPath string = "cmd/ilx_example/ilx/index.js" + indexjs, err := os.ReadFile(indexjsPath) + if err != nil { + fmt.Println("File reading error", err) + return + } + + // Write the index.js file to the extension. + err = f5.WriteExtensionFile(ctx, opts, string(indexjs), bigip.IndexJS) if err != nil { panic(err) } + // Read the index.js file from the extension. content, err := f5.ReadExtensionFile(ctx, opts, bigip.IndexJS) if err != nil { panic(err) } fmt.Printf("Content: %+v\n", content) + + // Write a TCL rule file to the workspace. + err = f5.WriteRuleFile(ctx, workspaceConfig, "", "Example.tcl") + if err != nil { + panic(err) + } + + // Read the TCL rule file from the workspace. + out, err := f5.ReadRuleFile(ctx, workspaceConfig, "Example.tcl") + if err != nil { + panic(err) + } + log.Printf("Rule: %v", out.Content) } diff --git a/ilx.go b/ilx.go index de59cb8..e94e79d 100644 --- a/ilx.go +++ b/ilx.go @@ -57,14 +57,8 @@ func (b *BigIP) DeleteWorkspace(ctx context.Context, name string) error { return nil } -type ExtensionConfig struct { - Name string `json:"name,omitempty"` - Partition string `json:"partition,omitempty"` - WorkspaceName string `json:"workspaceName,omitempty"` -} - func (b *BigIP) CreateExtension(ctx context.Context, opts ExtensionConfig) error { - err := b.post(ILXWorkspace{Name: opts.WorkspaceName}, uriMgmt, uriTm, uriIlx, uriWorkspace+"?options=extension,"+opts.Name) + err := b.post(ILXWorkspace{Name: opts.WorkspaceName}, uriMgmt, uriTm, uriIlx, uriWorkspace+"?options=extension,"+opts.ExtensionName) if err != nil { return fmt.Errorf("error creating ILX Extension: %w", err) } @@ -85,11 +79,30 @@ const ( IndexJS ExtensionFile = "index.js" ) +type WorkspaceConfig struct { + WorkspaceName string `json:"name,omitempty"` + Partition string `json:"partition,omitempty"` +} + +type ExtensionConfig struct { + WorkspaceConfig + ExtensionName string `json:"extensionName,omitempty"` +} + +func (b *BigIP) WriteRuleFile(ctx context.Context, opts WorkspaceConfig, content string, filename string) error { + destination := fmt.Sprintf("%s/%s/%s/rules/%s", WORKSPACE_UPLOAD_PATH, opts.Partition, opts.WorkspaceName, filename) + err := b.WriteFile(content, destination) + if err != nil { + return fmt.Errorf("error uploading rule file: %w", err) + } + return nil +} + func (b *BigIP) WriteExtensionFile(ctx context.Context, opts ExtensionConfig, content string, filename ExtensionFile) error { if err := filename.Validate(); err != nil { return err } - destination := fmt.Sprintf("%s/%s/%s/extensions/%s/%s", WORKSPACE_UPLOAD_PATH, opts.Partition, opts.WorkspaceName, opts.Name, filename) + destination := fmt.Sprintf("%s/%s/%s/extensions/%s/%s", WORKSPACE_UPLOAD_PATH, opts.WorkspaceConfig.Partition, opts.WorkspaceConfig.WorkspaceName, opts.ExtensionName, filename) err := b.WriteFile(content, destination) if err != nil { return fmt.Errorf("error uploading packagejson: %w", err) @@ -101,7 +114,16 @@ func (b *BigIP) ReadExtensionFile(ctx context.Context, opts ExtensionConfig, fil if err := filename.Validate(); err != nil { return nil, err } - destination := fmt.Sprintf("%s/%s/%s/extensions/%s/%s", WORKSPACE_UPLOAD_PATH, opts.Partition, opts.WorkspaceName, opts.Name, filename) + destination := fmt.Sprintf("%s/%s/%s/extensions/%s/%s", WORKSPACE_UPLOAD_PATH, opts.Partition, opts.WorkspaceConfig.WorkspaceName, opts.ExtensionName, filename) + files, err := b.ReadFile(destination) + if err != nil { + return nil, err + } + return files, nil +} + +func (b *BigIP) ReadRuleFile(ctx context.Context, opts WorkspaceConfig, filename string) (*ILXFile, error) { + destination := fmt.Sprintf("%s/%s/%s/rules/%s", WORKSPACE_UPLOAD_PATH, opts.Partition, opts.WorkspaceName, filename) files, err := b.ReadFile(destination) if err != nil { return nil, err From 7dcea5dd193de2884eac8cf6ab744782c6fb9f65 Mon Sep 17 00:00:00 2001 From: Dimitar Rusev Date: Mon, 16 Sep 2024 17:34:42 +0300 Subject: [PATCH 3/3] Change JS example --- cmd/ilx_example/ilx/index.js | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/cmd/ilx_example/ilx/index.js b/cmd/ilx_example/ilx/index.js index be4a0bb..f891109 100644 --- a/cmd/ilx_example/ilx/index.js +++ b/cmd/ilx_example/ilx/index.js @@ -1,26 +1,10 @@ -var f5 = require("f5-nodejs"); - +var f5 = require('f5-nodejs'); var ilx = new f5.ILXServer(); -ilx.addMethod("getCredentials", function (req, res) { - const [user] = req.params(); - const arc = new CyberArc(); - const credentials = arc.getCredentials(user); - res.reply(credentials); +ilx.addMethod('greeter', function(req, res) { + var arg = req.params()[0]; + + res.reply('Hello ' + arg); }); ilx.listen(); - -/** - * @class CyberArc - */ -class CyberArc { - /** - * @method getCredentials - * @param {string} user - * @returns {Array} [username, password] - */ - getCredentials(username) { - return ["admin", "password"]; - } -}