{"id":17355172,"url":"https://github.com/zephinzer/go-devops","last_synced_at":"2025-08-03T11:06:41.476Z","repository":{"id":79926648,"uuid":"408048781","full_name":"zephinzer/go-devops","owner":"zephinzer","description":null,"archived":false,"fork":false,"pushed_at":"2022-05-20T03:54:44.000Z","size":156,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-10T16:08:47.891Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zephinzer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"security/gpg/constants.go","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-09-19T06:18:00.000Z","updated_at":"2021-10-07T03:07:09.000Z","dependencies_parsed_at":"2023-09-17T12:00:32.559Z","dependency_job_id":null,"html_url":"https://github.com/zephinzer/go-devops","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/zephinzer/go-devops","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephinzer%2Fgo-devops","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephinzer%2Fgo-devops/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephinzer%2Fgo-devops/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephinzer%2Fgo-devops/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zephinzer","download_url":"https://codeload.github.com/zephinzer/go-devops/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephinzer%2Fgo-devops/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261073405,"owners_count":23105643,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-15T17:42:32.559Z","updated_at":"2025-06-21T06:06:29.000Z","avatar_url":"https://github.com/zephinzer.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-devops\n\nThis repository exports a package `devops` that simplifies writing of Go applications as internal tooling \"glue\".\n\n- [go-devops](#go-devops)\n- [Why you might want to use this](#why-you-might-want-to-use-this)\n- [Design principles](#design-principles)\n- [Usage and Examples](#usage-and-examples)\n  - [Commands](#commands)\n    - [Running a command](#running-a-command)\n  - [Input data](#input-data)\n    - [Download files](#download-files)\n    - [Get data from a HTTP endpoint](#get-data-from-a-http-endpoint)\n    - [Load configuration](#load-configuration)\n      - [Notes on loading configuration](#notes-on-loading-configuration)\n  - [Input validation](#input-validation)\n    - [Validating applications](#validating-applications)\n    - [Validating connections](#validating-connections)\n    - [Validating the environment](#validating-the-environment)\n    - [Validating project type](#validating-project-type)\n      - [Implementation notes for project type validation](#implementation-notes-for-project-type-validation)\n  - [Security](#security)\n    - [Generating an SSH keypair](#generating-an-ssh-keypair)\n    - [Retrieving the SSH key fingerprint](#retrieving-the-ssh-key-fingerprint)\n  - [User interactions](#user-interactions)\n    - [Confirmation dialog](#confirmation-dialog)\n- [Changelog](#changelog)\n- [License](#license)\n\n# Why you might want to use this\n\n1. You spend your time writing shell scripts and are sick of having untestable code (without significant effort)\n2. You are in a DevOps team moving towards a product way of doing things and have picked up Go an want to rewrite your shell scripts using Go\n\n# Design principles\n\n1. All `New[.]*` functions will return an `interface` as far as possible, while this could hide data, it also prevents state related errors from modifications after initialisation. This is in turn supported by validation checks that run during the initialisation process\n2. All `New[.]*` functions will perform a sanity check on provided options and return an `error` if checks are not successful. While this could be annoying, this encourages lazy-instantiation so that assigned properties do not become stale\n3. Rather than just providing methods to run a function, which would easily solve problems addressed above, we require a constructor for most objects via a method named `New[.]*` to allow for passing the instance to another controller, which means with this separation you can also separate your data access/creation and controller code by passing an instance to a controller for processing\n\n# Usage and Examples\n\nAll examples assume the importing of this package using:\n\n```go\n// ...\nimport \"gitlab.com/zephinzer/go-devops\"\n// ...\n```\n\n## Commands\n\n### Running a command\n\n\u003e A working example is available at [`./cmd/command`](./cmd/command)\n\nThe following runs `ls -al`:\n\n```go\nfunc main() {\n  ls, _ := devops.NewCommand(devops.NewCommandOpts{\n    Command: \"ls\",\n    Arguments: []string{\"-a\", \"-l\"},\n  })\n  ls.Run()\n}\n```\n\nThe following runs `go mod vendor` and pulls in dependencies for a Go project:\n\n```go\nfunc main() {\n  installGoDeps, _ := devops.NewCommand(devops.NewCommandOpts{\n    Command: \"go\",\n    Arguments: []string{\"mod\", \"vendor\"},\n  })\n  installGoDeps.Run()\n}\n```\n\nThe following runs `npm install` and pulls in dependencies for a Node project:\n\n```go\nfunc main() {\n  installNodeDeps, _ := devops.NewCommand(devops.NewCommandOpts{\n    Command: \"npm\",\n    Arguments: \"install\",\n  })\n  installNodeDeps.Run()\n}\n```\n\n## Input data\n\n### Download files\n\n\u003e A working example is available at [`./cmd/download`](./cmd/download)\n\nThe `.DownloadFile` method downloads the source code from Google into a specified `DestinationPath`:\n\n```go\nfunc main() {\n\ttargetURL, err := url.Parse(\"https://google.com\")\n  if err != nil {\n    panic(err)\n  }\n\tif err = devops.DownloadFile(DownloadFileOpts{\n\t\tDestinationPath: \"./google.com.src.txt\",\n\t\tURL:             targetURL,\n\t}); err != nil {\n    panic(err)\n  }\n}\n```\n\n### Get data from a HTTP endpoint\n\n\u003e A working example is available at [`./cmd/curl`](./cmd/curl)\n\nThe `.SendHTTPRequest` method can be used in place of `cURL` to make a HTTP request:\n\n```go\nfunc main() {\n\ttargetURL, err := url.Parse(\"https://httpbin.org/uuid\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tresponse, err := devops.SendHTTPRequest(devops.SendHTTPRequestOpts{\n\t\tURL: targetURL,\n\t})\n\tresponseBody, err := ioutil.ReadAll(response.Body)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Printf(\"uuid: %s\", string(responseBody))\n}\n```\n\n`.SendHTTPRequest` supports all common `curl` flags via the `SendHTTPRequestOpts` object.\n\n### Load configuration\n\n\u003e A working example is available at [`./cmd/configuration`](./cmd/configuration)\n\nThe `.LoadConfiguration` method allows you to load from environment variables using your own `struct` definition:\n\n```go\ntype configuration struct {\n  // CustomEnvString will be read using os.Getenv(\"USE_THIS_INSTEAD\")\n  CustomEnvString     string `env:\"USE_THIS_INSTEAD\"`\n  // RequiredStringSlice will be read using os.Getenv(\"REQUIRED_STRING_SLICE\")\n\tRequiredStringSlice []string  `default:\"a,b,c\" delimiter:\",\"`\n\tRequiredString      string    `default:\"hello world\"`\n\tRequiredInt         int       `default:\"1\"`\n\tRequiredBool        bool      `default:\"true\"`\n\tOptionalString      *string   `default:\"hola mundo\"`\n\tOptionalStringSlice *[]string `default:\"d,e,f\" delimiter:\",\"`\n\tOptionalInt         *int      `default:\"2\"`\n  OptionalBool        *bool     `default:\"true\"`\n}\n\nfunc main() {\n\tc := configuration{}\n\tif err := devops.LoadConfiguration(\u0026c); err != nil {\n    // use it like an error\n    log.Println(err)\n\n    // consolidated errors\n    errs := err.(devops.LoadConfigurationErrors)\n    log.Printf(\"error code   : %v\", errs.GetCode())\n    log.Printf(\"error message: %s\", errs.GetMessage())\n\n    // individual errors\n    log.Println(\"errors follow\")\n    for _, errInstance := range err.(devops.LoadConfigurationErrors) {\n      log.Printf(\"code   : %v\", errInstance.Code)\n      log.Printf(\"message: %s\", errInstance.Message)\n    }\n\n\t\tos.Exit(errs.GetCode())\n\t}\n}\n```\n\n#### Notes on loading configuration\n\n1. Property names are automagically converted to `UPPER_SNAKE_CASE` and these are used to load values from the environment using `os.Getenv`\n2. To define a custom environment key for the property, use the `env:\"READ_FROM_THIS_INSTEAD\"` struct tag\n3. To define a default value for the property, use the `default:\"default value\"` struct tag\n4. To indiciate a configuration property is **REQUIRED**, specify the type as a `value` type. If the environment does not contain the environment key, an error is returned\n5. To indiciate a configuration property is **OPTIONAL**, specify the type as a `*pointer` type. If the environment does not contain the environment key, the value is set to `nil`\n6. When defining a slice of strings, use the `delimiter:\",\"` struct tag to define the character sequence used to indicate boundaries between sequential strings\n7. The returned `error` can be type-asserted into a `LoadConfigurationErrors` structure which provides both a `GetCode()` and a `GetMessage()` method you can use for assessing errors, you could `range` through it to get individual errors or just call `.Error()` to get a collated error message\n\n## Input validation\n\n### Validating applications\n\nThe `.ValidateApplications` function can be used to validate that paths provided are executable or in the system's `$PATH` variable.\n\nA full example follows:\n\n```go\nfunc main() {\n\terr := devops.ValidateApplications(ValidateApplicationsOpts{\n\t\tPaths: []string{\"thisappdoesnotexist\"},\n\t})\n  if err != nil {\n    if _, ok := err.(devops.ValidateApplicationsErrors); ok {\n      panic(fmt.Sprintf(\"failed to find applications: ['%s']\", strings.Join(err.Errors, \"', '\")))\n    }\n  }\n}\n```\n\n### Validating connections\n\nThe `.ValidateConnection` function can be used to validate that a provided hostname and port is reachable and listening for requests.\n\nA full example follows:\n\n```go\nfunc main() {\n  err := devops.ValidateConnection(ValidateConnectionOpts{\n    Hostname: \"google.com\",\n    Port: 80,\n  })\n  if err != nil {\n    panic(err)\n  }\n}\n```\n\n### Validating the environment\n\nThe `.ValidateEnvironment` can be used to validate that certain keys of interest are defined in the enviornment and returns an error if it doesn't.\n\nA full example follows:\n\n```go\nfunc main() {\n\terr := ValidateEnvironment(ValidateEnvironmentOpts{\n\t\tKeys: EnvironmentKeys{\n\t\t\t{Name: \"STRING\", Type: TypeString},\n\t\t\t{Name: \"INT\", Type: TypeInt},\n\t\t\t{Name: \"UINT\", Type: TypeUint},\n\t\t\t{Name: \"FLOAT\", Type: TypeFloat},\n\t\t\t{Name: \"BOOL\", Type: TypeBool},\n\t\t\t{Name: \"ANY\", Type: TypeAny},\n\t\t},\n\t})\n  if err != nil {\n    panic(err)\n  }\n}\n```\n\nIf the `Type` property is not set, it defaults to `TypeAny`\n\nFor custom parsing of error, you can do a type assertion on the `error` interface to `ValidateEnvironmentErrors` and retrieve the error keys/types using the `.Errors` property:\n\n```go\nfunc main() {\n  err := devops.ValidateEnvironment(ValidateEnvironmentOpts{\n    Keys: EnvironmentKeys{\n\t\t\t{Name: \"STRING\", Type: TypeString},\n\t\t\t{Name: \"INT\", Type: TypeInt},\n\t\t\t{Name: \"UINT\", Type: TypeUint},\n\t\t\t{Name: \"FLOAT\", Type: TypeFloat},\n\t\t\t{Name: \"BOOL\", Type: TypeBool},\n\t\t\t{Name: \"ANY\", Type: TypeAny},\n    },\n  })\n  if err != nil {\n    errs, _ := err.(devops.ValidateEnvironmentErrors)\n    for _, errInstance := range errs.Errors {\n      fmt.Printf(\n        \"key[%s] errored (expected type: %s, observed value: %s)\",\n        errInstance.Key,\n        errInstance.ExpectedType,\n        errInstance.Value,\n      )\n    }\n  }\n}\n```\n\n### Validating project type\n\nThe `.IsProjectType` method allows you to test if a provided directory contains a project of the specified type.\n\nA full example follows which tests for a Go project:\n\n```go\nfunc main() {\n  yes, err := devops.IsProjectType(\"./path/to/dir\", devops.TypeGo)\n  if err != nil {\n    panic(err)\n  }\n  fmt.Printf(\"directory contains a go project: %v\", yes)\n}\n```\n\n#### Implementation notes for project type validation\n\n- Determination of project type is by detecting the presence of signature files commonly present in projects of that type\n\n## Security\n\n### Generating an SSH keypair\n\nTo generate an SSH keypair, you can use the `.NewSSHKeypair` function.\n\n```go\nfunc main() {\n  keypair, err := NewSSHKeypair(NewSSHKeypairOpts{\n    Bytes: 4096,\n  })\n  if err != nil {\n    panic(err)\n  }\n  // this prints the keys, you can write it to a file instead\n  fmt.Printf(\"private key: %s\\n\", string(keypair.Private))\n  fmt.Printf(\"public key : %s\\n\", string(keypair.Public))\n}\n```\n\n\n### Retrieving the SSH key fingerprint\n\n```go\nfunc main() {\n  keyPath := \"./tests/sshkeys/id_rsa_1024.pub\"\n  fingerprint, err := devops.GetSshKeyFingerprint(devops.GetSshKeyFingerprintOpts{\n    IsPublicKey: true,\n    Path:        keyPath,\n  })\n\n  fmt.Printf(\"md5 hash   : %s\\n\", fingerprint.GetMD5())\n  // above outputs 'aa:bb:cc:dd ...'\n\n  fmt.Printf(\"sha256 hash: %s\\n\", fingerprint.GetSHA256())\n  // above outputs 'sha256 hash: SHA256:AbCdEf ...'\n}\n```\n\nTo run this on a private key, set the `IsPublicKey` to `false` (or leave it unset) and set `IsPrivateKey` property to true.\n\nTo specify a password, set the `Passphrase` property of the `GetSshKeyFingerprintOpts` instance.\n\n## User interactions\n\n### Confirmation dialog\n\nTo trigger a confirmation dialog in the terminal with the user, use the `.Confirm` method.\n\n\u003e A working example is available at [`./cmd/confirm`](./cmd/confirm)\n\n```go\nfunc main() {\n  yes, err := devops.Confirm(devops.ConfirmOpts{\n    Question:   \"exact match\",\n    MatchExact: \"yes\",\n  })\n  if err != nil {\n    log.Fatalf(\"failed to get user input: %s\", err)\n  }\n  log.Printf(\"user confirmed: %v\\n\", yes)\n}\n```\n\n# Changelog\n\n| Version   | Changes                                                                                                                                 |\n| --------- | --------------------------------------------------------------------------------------------------------------------------------------- |\n| `v0.2.6`  | Refined issue with `NewCommand` that prevented it from dumping the derived path when `exec.LookPath` failed                             |\n| `v0.2.5`  | Fixed issue with `NewCommand` that prevented it from dumping the derived path when `exec.LookPath` failed                               |\n| `v0.2.4`  | Added `.IsProjectType`                                                                                                                  |\n| `v0.2.3`  | Added `.SendHTTPRequest`, improved inline documentation                                                                                 |\n| `v0.2.2`  | Added `.NewSSHKeypair`                                                                                                                  |\n| `v0.2.1`  | Fixed issues coming from `gosec`                                                                                                        |\n| `v0.2.0`  | Updated `error` return of `.LoadConfiguration` to return `LoadConfigurationErrors` instead so that all errors can be made known at once |\n| `v0.1.0`  | **Removed `.LoadEnvironment`** and added `.LoadConfiguration` which is a better and cleaner way of doing things                         |\n| `v0.0.13` | Formatting fixes                                                                                                                        |\n| `v0.0.12` | Added `.LoadEnvironment`                                                                                                                |\n| `v0.0.11` | Renamed module for being able to import it via its Gitlab URL                                                                           |\n| `v0.0.10` | Added `.ValidateConnection`                                                                                                             |\n| `v0.0.9`  | Added `.ValidateApplications`                                                                                                           |\n| `v0.0.8`  | Added `.DownloadFile`                                                                                                                   |\n| `v0.0.7`  | Added custom error parsing for `.ValidateEnvironment`                                                                                   |\n| `v0.0.6`  | Added `.ValidateEnvironment`                                                                                                            |\n| `v0.0.5`  | Added `.Confirm`                                                                                                                        |\n| `v0.0.4`  | Added inline code comments for documentation                                                                                            |\n| `v0.0.3`  | Added `.GetSshKeyFingerprint`. Also started changelog                                                                                   |\n\n# License\n\nCode is licensed under the MIT license. [See full license here](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzephinzer%2Fgo-devops","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzephinzer%2Fgo-devops","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzephinzer%2Fgo-devops/lists"}