зеркало из https://github.com/mozilla/mig.git
[minor] remove upgrade module and additional references to module
This commit is contained in:
Родитель
cf76532726
Коммит
bbd4b8d2e4
|
@ -140,23 +140,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"module": "upgrade",
|
|
||||||
"parameters": {
|
|
||||||
"to_version": "b9536d2-201403031435",
|
|
||||||
"location": "https://download.mig.example.net/mig-agent-b9536d2-201403031435",
|
|
||||||
"checksum": "c59d4eaeac728671c635ff645014e2afa935bebffdb5fbd207ffdeab"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module": "process",
|
|
||||||
"parameters": {
|
|
||||||
"look for running process that belongs to rootkits": [
|
|
||||||
"/usr/libexec/rootkitd",
|
|
||||||
"/opt/rootkit/stealth_dangerous"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"module": "agentdestroy",
|
"module": "agentdestroy",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"Name": "Upgrade a specific agent",
|
|
||||||
"Description": {
|
|
||||||
"Author": "Julien Vehent",
|
|
||||||
"Email": "jvehent@mozilla.com",
|
|
||||||
"Revision": 201408261000
|
|
||||||
},
|
|
||||||
"Target": "agents.environment->>'os' = 'linux' AND agents.environment->>'arch' = 'amd64'",
|
|
||||||
"Operations": [
|
|
||||||
{
|
|
||||||
"Module": "upgrade",
|
|
||||||
"Parameters": {
|
|
||||||
"linux/amd64": {
|
|
||||||
"to_version": "16eb58b-201404021544",
|
|
||||||
"location": "http://localhost/mig/bin/linux/amd64/mig-agent",
|
|
||||||
"checksum": "31fccc576635a29e0a27bbf7416d4f32a0ebaee892475e14708641c0a3620b03"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"SyntaxVersion": 2
|
|
||||||
}
|
|
1
agent.go
1
agent.go
|
@ -10,7 +10,6 @@ import "time"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AgtStatusOnline string = "online"
|
AgtStatusOnline string = "online"
|
||||||
AgtStatusUpgraded string = "upgraded"
|
|
||||||
AgtStatusDestroyed string = "destroyed"
|
AgtStatusDestroyed string = "destroyed"
|
||||||
AgtStatusOffline string = "offline"
|
AgtStatusOffline string = "offline"
|
||||||
AgtStatusIdle string = "idle"
|
AgtStatusIdle string = "idle"
|
||||||
|
|
|
@ -15,6 +15,5 @@ import (
|
||||||
_ "mig.ninja/mig/modules/pkg"
|
_ "mig.ninja/mig/modules/pkg"
|
||||||
_ "mig.ninja/mig/modules/scribe"
|
_ "mig.ninja/mig/modules/scribe"
|
||||||
_ "mig.ninja/mig/modules/timedrift"
|
_ "mig.ninja/mig/modules/timedrift"
|
||||||
//_ "mig/modules/upgrade"
|
|
||||||
//_ "mig/modules/example"
|
//_ "mig/modules/example"
|
||||||
)
|
)
|
||||||
|
|
|
@ -74,7 +74,7 @@ The following search parameters are available, per search type:
|
||||||
- investigatorname=<str>search agents that ran an action signed by investigator named <str>
|
- investigatorname=<str>search agents that ran an action signed by investigator named <str>
|
||||||
- version=<str> search agents by version <str>
|
- version=<str> search agents by version <str>
|
||||||
- status=<str> search agents with a given status amongst:
|
- status=<str> search agents with a given status amongst:
|
||||||
online, upgraded, destroyed, offline, idle
|
online, destroyed, offline, idle
|
||||||
* investigator:
|
* investigator:
|
||||||
- name=<str> search investigators by name
|
- name=<str> search investigators by name
|
||||||
- before=<rfc3339> search investigators created or modified before <rfc3339 date>
|
- before=<rfc3339> search investigators created or modified before <rfc3339 date>
|
||||||
|
|
|
@ -15,6 +15,5 @@ import (
|
||||||
_ "mig.ninja/mig/modules/pkg"
|
_ "mig.ninja/mig/modules/pkg"
|
||||||
_ "mig.ninja/mig/modules/scribe"
|
_ "mig.ninja/mig/modules/scribe"
|
||||||
_ "mig.ninja/mig/modules/timedrift"
|
_ "mig.ninja/mig/modules/timedrift"
|
||||||
//_ "mig/modules/upgrade"
|
|
||||||
//_ "mig/modules/example"
|
//_ "mig/modules/example"
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,6 +15,5 @@ import (
|
||||||
_ "mig.ninja/mig/modules/pkg"
|
_ "mig.ninja/mig/modules/pkg"
|
||||||
_ "mig.ninja/mig/modules/scribe"
|
_ "mig.ninja/mig/modules/scribe"
|
||||||
_ "mig.ninja/mig/modules/timedrift"
|
_ "mig.ninja/mig/modules/timedrift"
|
||||||
//_ "mig/modules/upgrade"
|
|
||||||
//_ "mig/modules/example"
|
//_ "mig/modules/example"
|
||||||
)
|
)
|
||||||
|
|
|
@ -130,7 +130,7 @@ func (db *DB) InsertAgent(agt mig.Agent, useTx *sql.Tx) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateAgentHeartbeat updates the heartbeat timestamp of an agent in the database
|
// UpdateAgentHeartbeat updates the heartbeat timestamp of an agent in the database
|
||||||
// unless the agent has been marked as destroyed or upgraded
|
// unless the agent has been marked as destroyed
|
||||||
func (db *DB) UpdateAgentHeartbeat(agt mig.Agent) (err error) {
|
func (db *DB) UpdateAgentHeartbeat(agt mig.Agent) (err error) {
|
||||||
_, err = db.c.Exec(`UPDATE agents SET status=$1, heartbeattime=$2 WHERE id=$3`,
|
_, err = db.c.Exec(`UPDATE agents SET status=$1, heartbeattime=$2 WHERE id=$3`,
|
||||||
mig.AgtStatusOnline, agt.HeartBeatTS, agt.ID)
|
mig.AgtStatusOnline, agt.HeartBeatTS, agt.ID)
|
||||||
|
@ -296,16 +296,6 @@ func (db *DB) ActiveAgentsByTarget(target string) (agents []mig.Agent, err error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkAgentUpgraded updated the status of an agent in the database
|
|
||||||
func (db *DB) MarkAgentUpgraded(agent mig.Agent) (err error) {
|
|
||||||
_, err = db.c.Exec(`UPDATE agents SET status=$1 WHERE id=$2`,
|
|
||||||
mig.AgtStatusUpgraded, agent.ID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to mark agent as upgraded in database: '%v'", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkAgentDestroyed updated the status and destructiontime of an agent in the database
|
// MarkAgentDestroyed updated the status and destructiontime of an agent in the database
|
||||||
func (db *DB) MarkAgentDestroyed(agent mig.Agent) (err error) {
|
func (db *DB) MarkAgentDestroyed(agent mig.Agent) (err error) {
|
||||||
agent.DestructionTime = time.Now()
|
agent.DestructionTime = time.Now()
|
||||||
|
|
|
@ -853,7 +853,7 @@ GET /api/v1/search
|
||||||
Status depends on the type. Below are the available statuses per type:
|
Status depends on the type. Below are the available statuses per type:
|
||||||
|
|
||||||
- `action`: pending, scheduled, preparing, invalid, inflight, completed
|
- `action`: pending, scheduled, preparing, invalid, inflight, completed
|
||||||
- `agent`: online, upgraded, destroyed, offline, idle
|
- `agent`: online, destroyed, offline, idle
|
||||||
- `command`: prepared, sent, success, timeout, cancelled, expired, failed
|
- `command`: prepared, sent, success, timeout, cancelled, expired, failed
|
||||||
- `investigator`: active, disabled
|
- `investigator`: active, disabled
|
||||||
|
|
||||||
|
|
|
@ -134,7 +134,6 @@ The list of modules imported in the agent is maintained in
|
||||||
_ "mig/modules/file"
|
_ "mig/modules/file"
|
||||||
_ "mig/modules/netstat"
|
_ "mig/modules/netstat"
|
||||||
_ "mig/modules/timedrift"
|
_ "mig/modules/timedrift"
|
||||||
//_ "mig/modules/upgrade"
|
|
||||||
_ "mig/modules/ping"
|
_ "mig/modules/ping"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,5 @@ import (
|
||||||
_ "mig.ninja/mig/modules/pkg"
|
_ "mig.ninja/mig/modules/pkg"
|
||||||
_ "mig.ninja/mig/modules/scribe"
|
_ "mig.ninja/mig/modules/scribe"
|
||||||
_ "mig.ninja/mig/modules/timedrift"
|
_ "mig.ninja/mig/modules/timedrift"
|
||||||
//_ "mig/modules/upgrade"
|
|
||||||
//_ "mig/modules/example"
|
//_ "mig/modules/example"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,330 +0,0 @@
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
|
|
||||||
|
|
||||||
// The upgrade module is used to download and install a new version of the
|
|
||||||
// mig-agent. It retrieves a binary from an HTTP location, validates its
|
|
||||||
// checksum. Verifies that the binary version is different from the currently
|
|
||||||
// running version. Install the binary and run it.
|
|
||||||
//
|
|
||||||
// At the end of the run, two mig-agent will be running on the same endpoint,
|
|
||||||
// and the scheduler will take care of killing one of them. This module does
|
|
||||||
// not attempt to kill the current mig-agent, in case the new one does not
|
|
||||||
// connect properly.
|
|
||||||
package upgrade /* import "mig.ninja/mig/modules/upgrade" */
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/kardianos/osext"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"mig.ninja/mig/modules"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type module struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *module) NewRun() modules.Runner {
|
|
||||||
return new(run)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
modules.Register("upgrade", new(module))
|
|
||||||
}
|
|
||||||
|
|
||||||
type run struct {
|
|
||||||
Parameters params
|
|
||||||
Results modules.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON sample:
|
|
||||||
// {
|
|
||||||
// "module": "upgrade",
|
|
||||||
// "parameters": {
|
|
||||||
// "to_version": "201403031435-b9536d2",
|
|
||||||
// "location": "https://download.mig.example.net/mig-agent-b9536d2-201403031435",
|
|
||||||
// "checksum": "c59d4eaeac728671c635ff645014e2afa935bebffdb5fbd207ffdeab"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
type params map[string]map[string]string
|
|
||||||
|
|
||||||
type elements struct {
|
|
||||||
OldPID int `json:"oldpid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var stats statistics
|
|
||||||
|
|
||||||
type statistics struct {
|
|
||||||
DownloadTime string `json:"downloadtime"`
|
|
||||||
DownloadSize int64 `json:"downloadsize"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r run) ValidateParameters() (err error) {
|
|
||||||
locre := regexp.MustCompile(`^https?://`)
|
|
||||||
checksumre := regexp.MustCompile(`^[a-zA-Z0-9]{64}$`)
|
|
||||||
for k, v := range r.Parameters {
|
|
||||||
if v["to_version"] == "" {
|
|
||||||
return fmt.Errorf("In %s, parameter 'to_version' is empty. Expecting version.", k, v["to_version"])
|
|
||||||
}
|
|
||||||
if !locre.MatchString(v["location"]) {
|
|
||||||
return fmt.Errorf("In %s, parameter 'location' with value '%s' is invalid. Expecting URL.", k, v["location"])
|
|
||||||
}
|
|
||||||
if !checksumre.MatchString(v["checksum"]) {
|
|
||||||
return fmt.Errorf("In %s, parameter 'checksum' with value '%s' is invalid. Expecting SHA256 checksum.", k, v["checksum"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r run) Run(in modules.ModuleReader) (out string) {
|
|
||||||
defer func() {
|
|
||||||
if e := recover(); e != nil {
|
|
||||||
r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", e))
|
|
||||||
r.Results.Success = false
|
|
||||||
buf, _ := json.Marshal(r.Results)
|
|
||||||
out = string(buf[:])
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
err := modules.ReadInputParameters(in, &r.Parameters)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = r.ValidateParameters()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the parameters that apply to this OS and Arch
|
|
||||||
key := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
|
|
||||||
el, ok := r.Parameters[key]
|
|
||||||
if !ok {
|
|
||||||
panic("no upgrade instruction found for " + key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the version we're told to upgrade to isn't the current one
|
|
||||||
cversion, err := getCurrentVersion()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if cversion == el["to_version"] {
|
|
||||||
panic("Agent is already running version " + cversion)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download new agent binary from provided location
|
|
||||||
binfd, err := downloadBinary(el["location"])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify checksum of the binary
|
|
||||||
err = verifyChecksum(binfd, el["checksum"])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// grab the path before closing the file descriptor
|
|
||||||
binPath := binfd.Name()
|
|
||||||
|
|
||||||
err = binfd.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dry run of the binary to verify that the version is correct
|
|
||||||
// but also that it can run without error
|
|
||||||
err = verifyVersion(binPath, el["to_version"])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the binary of the new agent from tmp, to the correct destination
|
|
||||||
agentBinPath, err := moveBinary(binPath, el["to_version"])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Launch the new agent and exit the module
|
|
||||||
cmd := exec.Command(agentBinPath, "-u")
|
|
||||||
err = cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
out = r.buildResults()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the agent binary to obtain the current version
|
|
||||||
func getCurrentVersion() (cversion string, err error) {
|
|
||||||
defer func() {
|
|
||||||
if e := recover(); e != nil {
|
|
||||||
err = fmt.Errorf("getCurrentVersion() -> %v", e)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
bin, err := osext.Executable()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
out, err := exec.Command(bin, "-V").Output()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if len(out) < 2 {
|
|
||||||
panic("Failed to retrieve agent version.")
|
|
||||||
}
|
|
||||||
cversion = string(out[:len(out)-1])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// downloadBinary retrieves the data from a location and saves it to a temp file
|
|
||||||
func downloadBinary(loc string) (tmpfd *os.File, err error) {
|
|
||||||
defer func() {
|
|
||||||
if e := recover(); e != nil {
|
|
||||||
err = fmt.Errorf("downloadBinary() -> %v", e)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
tmpfd, err = ioutil.TempFile("", "")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
start := time.Now()
|
|
||||||
resp, err := http.Get(loc)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
stats.DownloadSize, err = io.Copy(tmpfd, resp.Body)
|
|
||||||
stats.DownloadTime = time.Since(start).String()
|
|
||||||
resp.Body.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyChecksum computes the hash of a file and compares it
|
|
||||||
// to a checksum. If comparison fails, it returns an error.
|
|
||||||
func verifyChecksum(fd *os.File, checksum string) (err error) {
|
|
||||||
defer func() {
|
|
||||||
if e := recover(); e != nil {
|
|
||||||
err = fmt.Errorf("verifyChecksum() -> %v", e)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
var h hash.Hash
|
|
||||||
h = sha256.New()
|
|
||||||
buf := make([]byte, 4096)
|
|
||||||
var offset int64 = 0
|
|
||||||
for {
|
|
||||||
block, err := fd.ReadAt(buf, offset)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if block == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
h.Write(buf[:block])
|
|
||||||
offset += int64(block)
|
|
||||||
}
|
|
||||||
hexhash := fmt.Sprintf("%x", h.Sum(nil))
|
|
||||||
if hexhash != checksum {
|
|
||||||
return fmt.Errorf("Checksum validation failed. Got '%s', Expected '%s'.",
|
|
||||||
hexhash, checksum)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyVersion runs a binary and compares the returned version
|
|
||||||
func verifyVersion(binPath, expectedVersion string) (err error) {
|
|
||||||
defer func() {
|
|
||||||
if e := recover(); e != nil {
|
|
||||||
err = fmt.Errorf("verifyVersion() -> %v", e)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
os.Chmod(binPath, 0500)
|
|
||||||
out, err := exec.Command(binPath, "-V").Output()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
binVersion := string(out[:len(out)-1])
|
|
||||||
if binVersion != expectedVersion {
|
|
||||||
return fmt.Errorf("Version mismatch. Got '%s', Expected '%s'.",
|
|
||||||
binVersion, expectedVersion)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func moveBinary(binPath, version string) (linkloc string, err error) {
|
|
||||||
defer func() {
|
|
||||||
if e := recover(); e != nil {
|
|
||||||
err = fmt.Errorf("moveBinary() -> %v", e)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
var target string
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux", "darwin", "freebsd", "openbsd", "netbsd":
|
|
||||||
target = fmt.Sprintf("/sbin/mig-agent-%s", version)
|
|
||||||
linkloc = "/sbin/mig-agent"
|
|
||||||
case "windows":
|
|
||||||
target = fmt.Sprintf("C:\\Program Files\\mig\\mig-agent-%s.exe", version)
|
|
||||||
linkloc = "C:\\Program Files\\mig\\mig-agent.exe"
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("'%s' isn't a supported OS", runtime.GOOS)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// copy the file (rename may not work if we're crossing partitions)
|
|
||||||
srcfd, err := os.Open(binPath)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
dstfd, err := os.Create(target)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
_, err = io.Copy(dstfd, srcfd)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
srcfd.Close()
|
|
||||||
dstfd.Close()
|
|
||||||
err = os.Remove(binPath)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
err = os.Chmod(target, 0750)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// don't fail on removal of existing link, it may not exist
|
|
||||||
os.Remove(linkloc)
|
|
||||||
// create a symlink
|
|
||||||
err = os.Symlink(target, linkloc)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildResults transforms the ConnectedIPs map into a Results
|
|
||||||
// map that is serialized in JSON and returned as a string
|
|
||||||
func (r run) buildResults() string {
|
|
||||||
var el elements
|
|
||||||
el.OldPID = os.Getppid()
|
|
||||||
r.Results.Elements = el
|
|
||||||
if len(r.Results.Errors) == 0 {
|
|
||||||
r.Results.Success = true
|
|
||||||
}
|
|
||||||
r.Results.Statistics = stats
|
|
||||||
jsonOutput, err := json.Marshal(r.Results)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return string(jsonOutput[:])
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Contributor: Dustin J. Mitchell <dustin@mozilla.com>
|
|
||||||
|
|
||||||
package upgrade /* import "mig.ninja/mig/modules/upgrade" */
|
|
||||||
|
|
||||||
import (
|
|
||||||
"mig.ninja/mig/testutil"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRegistration(t *testing.T) {
|
|
||||||
testutil.CheckModuleRegistration(t, "upgrade")
|
|
||||||
}
|
|
Загрузка…
Ссылка в новой задаче