Compare commits
22 Commits
Author | SHA1 | Date |
---|---|---|
Adrien Marquès | 5998bd7b29 | |
Adrien Marquès | 27ace57523 | |
Adrien Marquès | a3b513a23b | |
Adrien Marquès | b752906d15 | |
Adrien Marquès | 3331970eb5 | |
Adrien Marquès | bca2b145bc | |
Adrien Marquès | 0059dd90ea | |
Adrien Marquès | c37893c209 | |
Adrien Marquès | 5f229709ad | |
Adrien Marquès | 3c598ea419 | |
Adrien Marquès | bf391fd5a0 | |
Adrien Marquès | b26dec8576 | |
Adrien Marquès | 8c3b300ab5 | |
Adrien Marquès | 2e3700786d | |
Adrien Marquès | fa8a0a5ae5 | |
Adrien Marquès | 1a7e1c2db0 | |
Adrien Marquès | 2f95acb851 | |
Adrien Marquès | a6207e0a34 | |
Adrien Marquès | 5fd41cae67 | |
Adrien Marquès | 7e8b45d750 | |
Adrien Marquès | 361c3f6c25 | |
Adrien Marquès | 87bf394536 |
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: test
|
||||||
|
image: golang:1.13
|
||||||
|
commands:
|
||||||
|
- go get ./...
|
||||||
|
- go test -v -race -cover -coverprofile ./coverage.out ./...
|
77
README.md
77
README.md
|
@ -1,11 +1,11 @@
|
||||||
# | nix-amer |
|
# | nix-amer |
|
||||||
|
|
||||||
[![Go version](https://img.shields.io/badge/go_version-1.11-blue.svg)](https://golang.org/doc/go1.11)
|
[![Go version](https://img.shields.io/badge/go_version-1.11-blue.svg)](https://golang.org/doc/go1.11)
|
||||||
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
|
[![License: MIT](https://img.shields.io/github/license/xdrm-brackets/nix-amer.svg)](https://opensource.org/licenses/MIT)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/xdrm-brackets/nix-amer)](https://goreportcard.com/report/github.com/xdrm-brackets/nix-amer)
|
[![Go Report Card](https://goreportcard.com/badge/git.xdrm.io/go/nix-amer)](https://goreportcard.com/report/git.xdrm.io/go/nix-amer)
|
||||||
[![Coverage Status](https://coveralls.io/repos/github/xdrm-brackets/nix-amer/badge.svg?branch=master)](https://coveralls.io/github/xdrm-brackets/nix-amer?branch=master)
|
[![Coverage Status](https://img.shields.io/coveralls/github/xdrm-brackets/nix-amer/master.svg)](https://coveralls.io/github/xdrm-brackets/nix-amer?branch=master)
|
||||||
[![CircleCI Build Status](https://circleci.com/gh/xdrm-brackets/nix-amer.svg?style=shield)](https://circleci.com/gh/xdrm-brackets/nix-amer)
|
[![Build Status](https://drone.xdrm.io/api/badges/go/nix-amer/status.svg)](https://drone.xdrm.io/go/nix-amer)
|
||||||
[![Go doc](https://godoc.org/github.com/xdrm-brackets/nix-amer?status.svg)](https://godoc.org/github.com/xdrm-brackets/nix-amer)
|
[![Go doc](https://godoc.org/git.xdrm.io/go/nix-amer?status.svg)](https://godoc.org/git.xdrm.io/go/nix-amer)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: nix-amer
|
name: nix-amer
|
||||||
|
@ -25,13 +25,14 @@ author: xdrm-brackets
|
||||||
* [1. Create build file](#1-create-build-file)
|
* [1. Create build file](#1-create-build-file)
|
||||||
* [2. Run on the target](#2-run-on-the-target)
|
* [2. Run on the target](#2-run-on-the-target)
|
||||||
* [II. Commands](#ii-commands)
|
* [II. Commands](#ii-commands)
|
||||||
+ [1) Comments](#1-comments)
|
+ [1) Sections](#1-sections)
|
||||||
+ [2) Install/remove Packages](#2-installremove-packages)
|
+ [2) Comments](#2-comments)
|
||||||
+ [3) Setup configuration](#3-setup-configuration)
|
+ [3) Install/remove Packages](#3-installremove-packages)
|
||||||
+ [4) Service management](#4-service-management)
|
+ [4) Setup configuration](#4-setup-configuration)
|
||||||
+ [5) Custom scripts](#5-custom-scripts)
|
+ [5) Service management](#5-service-management)
|
||||||
+ [6) Copy files](#6-copy-files)
|
+ [6) Custom scripts](#6-custom-scripts)
|
||||||
+ [7) Aliases](#7-aliases)
|
+ [7) Copy files](#7-copy-files)
|
||||||
|
+ [8) Aliases](#8-aliases)
|
||||||
* [III. Path Expressions](#iii-path-expressions)
|
* [III. Path Expressions](#iii-path-expressions)
|
||||||
+ [1) Syntax](#1-syntax)
|
+ [1) Syntax](#1-syntax)
|
||||||
+ [2) File Formats](#2-file-formats)
|
+ [2) File Formats](#2-file-formats)
|
||||||
|
@ -59,10 +60,10 @@ In order to install the `nix-amer` executable, you must have :
|
||||||
Simply launch the following command in any terminal
|
Simply launch the following command in any terminal
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ go get -u github.com/xdrm-brackets/nix-amer
|
$ go get -u git.xdrm.io/go/nix-amer
|
||||||
```
|
```
|
||||||
|
|
||||||
> For those who don't know, it will load the project sources into `$GOPATH/src/github.com/xdrm-brackets/nix-amer` and compile into the executable at `$GOPATH/bin/nix-amer`.
|
> For those who don't know, it will load the project sources into `$GOPATH/src/git.xdrm.io/go/nix-amer` and compile into the executable at `$GOPATH/bin/nix-amer`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,7 +71,7 @@ $ go get -u github.com/xdrm-brackets/nix-amer
|
||||||
|
|
||||||
###### 1. Create build file
|
###### 1. Create build file
|
||||||
|
|
||||||
The first step is to write your build file according to the installation you want. While writing your build file you can check the syntax by using the `-dry-run` command-line argument as follows :
|
The first step is to write your build file according to the installation you want. While writing it you can check the syntax and validate instructions by using the `-dry-run` command-line argument as follows :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ nix-amer -p apt-get -dry-run <path/to/build/file>
|
$ nix-amer -p apt-get -dry-run <path/to/build/file>
|
||||||
|
@ -82,7 +83,7 @@ $ nix-amer -p apt-get -dry-run <path/to/build/file>
|
||||||
|
|
||||||
###### 2. Run on the target
|
###### 2. Run on the target
|
||||||
|
|
||||||
Once your build file is correct and fulfills your needs, you can log in to the target machine, install the nix-amer and run it with your build file. Nix-amer's rich and colorful command-line output will give you a good feedback to rapidly fix problems.
|
Once your build file is correct and fulfills your needs, you can log in to the target machine, install nix-amer and run it with your build file. The rich and colorful command-line output will give you a good feedback to rapidly fix problems.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,17 +95,23 @@ Once your build file is correct and fulfills your needs, you can log in to the t
|
||||||
|
|
||||||
### II. Commands
|
### II. Commands
|
||||||
|
|
||||||
Your whole setup remains in 1 only build file. Each line contains one instruction, the list of instructions is listed below.
|
Your whole setup remains in only one file. Each line contains one instruction, the list of instructions is listed below.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 1) Comments
|
#### 1) Sections
|
||||||
|
|
||||||
Each line beginning with one of the following characters : `[`, `#` or `;` is considered a comment and is not interpreted.
|
Each instruction is enclosed in a section (_cf. ini file format_), a section definition stands on a line where the name of the section is surrounded by `[` and `]`. Each section is executed in parallel ; the special section named `pre` is executed before every other.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 2) Install/remove Packages
|
#### 2) Comments
|
||||||
|
|
||||||
|
Each line beginning with one of the following characters : `#` or `;` is considered a comment and is not interpreted.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 3) Install/remove Packages
|
||||||
|
|
||||||
These instructions allow you to interact with the package system available on your system.
|
These instructions allow you to interact with the package system available on your system.
|
||||||
|
|
||||||
|
@ -122,7 +129,7 @@ Remove the listed packages. If more than one, use spaces to separate package nam
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 3) Setup configuration
|
#### 4) Setup configuration
|
||||||
|
|
||||||
This instruction allow you to set fields of configuration files without the need of an editor and in a developer-readable manner.
|
This instruction allow you to set fields of configuration files without the need of an editor and in a developer-readable manner.
|
||||||
|
|
||||||
|
@ -134,7 +141,7 @@ Update a configuration file where \<expr\> is a dot-separated human-readable [pa
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 4) Service management
|
#### 5) Service management
|
||||||
|
|
||||||
These instructions allow you to interact with the service system (_cf. [systemd](https://github.com/systemd/systemd)_).
|
These instructions allow you to interact with the service system (_cf. [systemd](https://github.com/systemd/systemd)_).
|
||||||
|
|
||||||
|
@ -145,7 +152,7 @@ Perform the action on services. If more than one, use spaces to separate service
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 5) Custom scripts
|
#### 6) Custom scripts
|
||||||
|
|
||||||
This instruction allows you to use custom scripts for complex operations.
|
This instruction allows you to use custom scripts for complex operations.
|
||||||
|
|
||||||
|
@ -153,9 +160,9 @@ This instruction allows you to use custom scripts for complex operations.
|
||||||
run <script>
|
run <script>
|
||||||
```
|
```
|
||||||
|
|
||||||
Execute the executable located at the path \<script\>. If script is an [alias](#7-aliases) it will resolve to its path
|
Execute the executable located at the path \<script\>. If script is an [alias](#8-aliases) it will resolve to its path
|
||||||
|
|
||||||
#### 6) Copy files
|
#### 7) Copy files
|
||||||
|
|
||||||
This instruction allows you to copy files.
|
This instruction allows you to copy files.
|
||||||
|
|
||||||
|
@ -167,7 +174,7 @@ Try to copy the file \<src\> to the path \<dst\>.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 7) Aliases
|
#### 8) Aliases
|
||||||
|
|
||||||
The file format allows you to create aliases to file paths for more readability in the [path expression](#ii-path-expressions) or with the [`run` command](#5-custom-scripts).
|
The file format allows you to create aliases to file paths for more readability in the [path expression](#ii-path-expressions) or with the [`run` command](#5-custom-scripts).
|
||||||
|
|
||||||
|
@ -195,7 +202,7 @@ The syntax is pretty fast-forward, it uses 2 levels (file, fields) to find your
|
||||||
|
|
||||||
| Field | Description | Example |
|
| Field | Description | Example |
|
||||||
| --------- | :----------------------------------- | -------------------------- |
|
| --------- | :----------------------------------- | -------------------------- |
|
||||||
| `location_or_alias` | Path to the configuration file to edit. The file will be created if not found. If the path is an [alias](#7-aliases) created before in the file, it will resolve to the alias value as a filename. | `/etc/nginx/nginx.conf`, `some-alias` |
|
| `location_or_alias` | Path to the configuration file to edit. The file will be created if not found. If the path is an [alias](#8-aliases) created before in the file, it will resolve to the alias value as a filename. | `/etc/nginx/nginx.conf`, `some-alias` |
|
||||||
| `fields` | Dot-separated chain of strings that match a configuration field. If the field does not point to a raw field but an existing field container, the \<value\> will replace the group with a text value. | `AllowGroups`, `http.gzip` |
|
| `fields` | Dot-separated chain of strings that match a configuration field. If the field does not point to a raw field but an existing field container, the \<value\> will replace the group with a text value. | `AllowGroups`, `http.gzip` |
|
||||||
|
|
||||||
> The `fields` is processed only for known file formats listed in this [section](#2-file-formats).
|
> The `fields` is processed only for known file formats listed in this [section](#2-file-formats).
|
||||||
|
@ -212,8 +219,8 @@ Configuration files can be written according to some standards or application-sp
|
||||||
|
|
||||||
- [yaml](https://en.wikipedia.org/wiki/YAML) with [go-yaml/yaml](https://github.com/go-yaml/yaml).
|
- [yaml](https://en.wikipedia.org/wiki/YAML) with [go-yaml/yaml](https://github.com/go-yaml/yaml).
|
||||||
- [ini](https://en.wikipedia.org/wiki/INI_file) with [go-ini/ini](https://github.com/go-ini/ini).
|
- [ini](https://en.wikipedia.org/wiki/INI_file) with [go-ini/ini](https://github.com/go-ini/ini).
|
||||||
|
- [nginx configurations](https://docs.nginx.com/nginx/admin-guide/basic-functionality/managing-configuration-files/) with [my own library](https://godoc.org/git.xdrm.io/go/nix-amer/internal/cnf/parser/nginx).
|
||||||
- [nginx configurations](https://docs.nginx.com/nginx/admin-guide/basic-functionality/managing-configuration-files/) with [my own library](https://godoc.org/github.com/xdrm-brackets/nix-amer/internal/cnf/parser/nginx).
|
- [bash sourced configurations]() with [my own library](https://godoc.org/git.xdrm.io/go/nix-amer/internal/cnf/parser/bash) (_e.g. ~/.bashrc_).
|
||||||
- _and more to come..._
|
- _and more to come..._
|
||||||
|
|
||||||
|
|
||||||
|
@ -235,17 +242,13 @@ $ nix-amer -p apt-get myserver.build
|
||||||
_myserver.build_
|
_myserver.build_
|
||||||
|
|
||||||
```
|
```
|
||||||
[ comment starts with opening brackets '['
|
# [pre] is executed before launching everything else
|
||||||
|
[pre]
|
||||||
[aliases]
|
install nginx ssh sslh
|
||||||
alias sshd /etc/ssh/sshd_config
|
|
||||||
alias nginx /etc/nginx/nginx.conf
|
alias nginx /etc/nginx/nginx.conf
|
||||||
|
alias sshd /etc/ssh/sshd_config
|
||||||
alias sslh /etc/default/sslh
|
alias sslh /etc/default/sslh
|
||||||
|
|
||||||
[install packages]
|
|
||||||
install nginx ssh
|
|
||||||
install sslh
|
|
||||||
|
|
||||||
[nginx]
|
[nginx]
|
||||||
set nginx@http.gzip off
|
set nginx@http.gzip off
|
||||||
service enable nginx
|
service enable nginx
|
||||||
|
|
2
args.go
2
args.go
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/instruction"
|
"git.xdrm.io/go/nix-amer/internal/instruction"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetArgs manages cli arguments to build executionContext,
|
// GetArgs manages cli arguments to build executionContext,
|
||||||
|
|
38
circle.yml
38
circle.yml
|
@ -1,38 +0,0 @@
|
||||||
version: 2
|
|
||||||
jobs:
|
|
||||||
build: # runs not using Workflows must have a `build` job as entry point
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang
|
|
||||||
|
|
||||||
environment: # environment variables for the build itself
|
|
||||||
GOPATH: /go
|
|
||||||
TEST_RESULTS: /tmp/test-results
|
|
||||||
COVER_PROFILE: /tmp/coverage.out
|
|
||||||
|
|
||||||
steps: # steps that comprise the `build` job
|
|
||||||
- checkout # check out source code to working directory
|
|
||||||
- run: mkdir -p $TEST_RESULTS # create the test results directory
|
|
||||||
|
|
||||||
- restore_cache: # restores saved cache if no changes are detected since last run
|
|
||||||
keys:
|
|
||||||
- v1-pkg-cache
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Load dependencies
|
|
||||||
command: go get github.com/mattn/goveralls && go get github.com/go-ini/ini && go get gopkg.in/yaml.v2
|
|
||||||
- run:
|
|
||||||
name: Load nix-amer
|
|
||||||
command: go get github.com/xdrm-brackets/nix-amer
|
|
||||||
- run:
|
|
||||||
name: Unit tests
|
|
||||||
command: go test -v -cover -race -coverprofile=$COVER_PROFILE github.com/xdrm-brackets/nix-amer/...
|
|
||||||
- run:
|
|
||||||
name: Update coveralls.io
|
|
||||||
command: /go/bin/goveralls -coverprofile=$COVER_PROFILE -service=circle-ci -repotoken=$COVERALLS_TOKEN
|
|
||||||
|
|
||||||
- store_artifacts: # Upload test summary for display in Artifacts
|
|
||||||
path: /tmp/test-results
|
|
||||||
destination: raw-test-output
|
|
||||||
|
|
||||||
- store_test_results: # Upload test results for display in Test Summary
|
|
||||||
path: /tmp/test-results
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
module git.xdrm.io/go/nix-amer
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-ini/ini v1.51.0
|
||||||
|
gopkg.in/yaml.v2 v2.2.5
|
||||||
|
)
|
|
@ -0,0 +1,5 @@
|
||||||
|
github.com/go-ini/ini v1.51.0 h1:VPJKXGzbKlyExUE8f41aV57yxkYx5R49yR6n7flp0M0=
|
||||||
|
github.com/go-ini/ini v1.51.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
||||||
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
2
help.go
2
help.go
|
@ -2,7 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/clifmt"
|
"git.xdrm.io/go/nix-amer/internal/clifmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func help() {
|
func help() {
|
||||||
|
|
|
@ -2,7 +2,7 @@ package buildfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/clifmt"
|
"git.xdrm.io/go/nix-amer/internal/clifmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LineError wraps errors with a line index
|
// LineError wraps errors with a line index
|
||||||
|
|
|
@ -4,24 +4,43 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/clifmt"
|
"git.xdrm.io/go/nix-amer/internal/clifmt"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/instruction"
|
"git.xdrm.io/go/nix-amer/internal/instruction"
|
||||||
"io"
|
"io"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNullContext is raised when the given context is nil
|
// ErrNullContext is raised when the given context is nil
|
||||||
var ErrNullContext = errors.New("null context")
|
var ErrNullContext = errors.New("null context")
|
||||||
|
|
||||||
|
// ErrNoParent is raised when there is an instruction but has no parent section
|
||||||
|
var ErrNoParent = errors.New("missing parent section")
|
||||||
|
|
||||||
// Reader is the buildfile reader
|
// Reader is the buildfile reader
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
// Context is the linux distribution-specified execution context (package manager, service manager, etc)
|
// Context is the linux distribution-specified execution context (package manager, service manager, etc)
|
||||||
Context *instruction.ExecutionContext
|
Context *instruction.ExecutionContext
|
||||||
// Content is the instruction list
|
// Content is the instruction list
|
||||||
Content []instruction.T
|
Content map[string]*[]instruction.T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type execStatus struct {
|
||||||
|
name string
|
||||||
|
start time.Time
|
||||||
|
stop time.Time
|
||||||
|
stopped bool
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
type tableSection struct {
|
||||||
|
name string
|
||||||
|
instructions []execStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
var reSection = regexp.MustCompile(`(?m)^\[\s*([a-z0-9_-]+)\s*\]$`)
|
||||||
|
|
||||||
// NewReader creates a new reader for the specified build file and linux distribution
|
// NewReader creates a new reader for the specified build file and linux distribution
|
||||||
func NewReader(ctx *instruction.ExecutionContext, buildfile io.Reader) (*Reader, error) {
|
func NewReader(ctx *instruction.ExecutionContext, buildfile io.Reader) (*Reader, error) {
|
||||||
|
|
||||||
|
@ -32,36 +51,60 @@ func NewReader(ctx *instruction.ExecutionContext, buildfile io.Reader) (*Reader,
|
||||||
|
|
||||||
r := &Reader{
|
r := &Reader{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Content: make([]instruction.T, 0),
|
Content: make(map[string]*[]instruction.T),
|
||||||
}
|
}
|
||||||
|
|
||||||
// add each line as instruction
|
// add each line as instruction
|
||||||
l, reader := 0, bufio.NewReader(buildfile)
|
l, reader := 0, bufio.NewReader(buildfile)
|
||||||
|
eof := false
|
||||||
|
var section *[]instruction.T // current section
|
||||||
for {
|
for {
|
||||||
l++
|
l++
|
||||||
|
if eof {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
// read line until end
|
// 1. read line until end
|
||||||
line, err := reader.ReadString('\n')
|
line, err := reader.ReadString('\n')
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
if len(line) > 0 {
|
||||||
|
eof = true
|
||||||
|
} else {
|
||||||
break
|
break
|
||||||
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, LineError{l, err}
|
return nil, LineError{l, err}
|
||||||
}
|
}
|
||||||
line = strings.Trim(line, " \t\r\n")
|
line = strings.Trim(line, " \t\r\n")
|
||||||
|
|
||||||
// ignore newline & comments
|
// 2. ignore newline & comments
|
||||||
if len(line) < 1 || strings.ContainsAny(line[0:1], "[#;") {
|
if len(line) < 1 || strings.ContainsAny(line[0:1], "#;") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// turn into instruction
|
// 3. section
|
||||||
|
if match := reSection.FindStringSubmatch(line); len(match) > 1 {
|
||||||
|
// already in section
|
||||||
|
sec := make([]instruction.T, 0)
|
||||||
|
section = &sec
|
||||||
|
r.Content[match[1]] = section
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. fail if no parent section
|
||||||
|
if section == nil {
|
||||||
|
return nil, ErrNoParent
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. create instruction
|
||||||
inst, err := instruction.Parse(line)
|
inst, err := instruction.Parse(line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, LineError{l, err}
|
return nil, LineError{l, err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to list
|
// add to list
|
||||||
r.Content = append(r.Content, inst)
|
*section = append(*section, inst)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +112,13 @@ func NewReader(ctx *instruction.ExecutionContext, buildfile io.Reader) (*Reader,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the current buildfile instruction by instruction
|
// Execute the current buildfile instruction by instruction
|
||||||
func (r *Reader) Execute() error {
|
// if <dryRun> is set to TRUE, run on dry-run mode
|
||||||
|
func (r *Reader) Execute(_dryRun ...bool) error {
|
||||||
|
|
||||||
|
dryRun := true
|
||||||
|
if len(_dryRun) < 1 || !_dryRun[0] {
|
||||||
|
dryRun = false
|
||||||
|
}
|
||||||
|
|
||||||
// 1. update package list
|
// 1. update package list
|
||||||
// err := r.Context.PackageManager.Fetch()
|
// err := r.Context.PackageManager.Fetch()
|
||||||
|
@ -82,26 +131,132 @@ func (r *Reader) Execute() error {
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return fmt.Errorf("cannot upgrade | %s", err)
|
// return fmt.Errorf("cannot upgrade | %s", err)
|
||||||
// }
|
// }
|
||||||
|
refresh := make(chan bool, 1)
|
||||||
|
wg := new(sync.WaitGroup)
|
||||||
|
wgstatus := new(sync.WaitGroup)
|
||||||
|
|
||||||
// 3. exec each instruction
|
// 1. create status table + extract [pre] section if one
|
||||||
for i, inst := range r.Content {
|
table := make([]tableSection, 0)
|
||||||
clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(0, inst.Raw())))
|
index := make(map[string]int, 0)
|
||||||
fmt.Printf("%s", clifmt.Color(33, "processing"))
|
|
||||||
|
|
||||||
start := time.Now()
|
var pre *[]instruction.T
|
||||||
|
var preTable *tableSection
|
||||||
|
|
||||||
_, err := inst.Exec(*r.Context)
|
for secname, sec := range r.Content {
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("\r")
|
tableSec := tableSection{
|
||||||
clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(0, inst.Raw())))
|
name: secname,
|
||||||
fmt.Printf("%s \n", clifmt.Color(31, err.Error()))
|
instructions: make([]execStatus, len(*sec), len(*sec)+1),
|
||||||
|
}
|
||||||
|
|
||||||
|
// for each instruction
|
||||||
|
for i, inst := range *sec {
|
||||||
|
tableSec.instructions[i].name = inst.Raw()
|
||||||
|
}
|
||||||
|
|
||||||
|
table = append(table, tableSec)
|
||||||
|
index[secname] = len(table) - 1
|
||||||
|
|
||||||
|
// [pre] section
|
||||||
|
if secname == "pre" {
|
||||||
|
pre = sec
|
||||||
|
preTable = &tableSec
|
||||||
|
}
|
||||||
|
|
||||||
|
// add one section
|
||||||
|
wg.Add(len(*sec))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. launch status updater
|
||||||
|
wgstatus.Add(1)
|
||||||
|
go status(table, refresh, wgstatus)
|
||||||
|
|
||||||
|
// 3. launch [pre] (it set)
|
||||||
|
if pre != nil {
|
||||||
|
execSection(pre, *r.Context, preTable, dryRun, refresh, wg)
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. launch each other section
|
||||||
|
for secname, sec := range r.Content {
|
||||||
|
// do not launch pre again
|
||||||
|
if secname == "pre" {
|
||||||
continue
|
continue
|
||||||
} else {
|
|
||||||
fmt.Printf("\r")
|
|
||||||
clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(34, inst.Raw())))
|
|
||||||
fmt.Printf("%ss \n", clifmt.Color(32, fmt.Sprintf("%.2f", time.Now().Sub(start).Seconds())))
|
|
||||||
}
|
}
|
||||||
|
i, ok := index[secname]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
go execSection(sec, *r.Context, &table[i], dryRun, refresh, wg)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(refresh)
|
||||||
|
wgstatus.Wait()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func execSection(section *[]instruction.T, ctx instruction.ExecutionContext, tsec *tableSection, dryRun bool, refresher chan<- bool, wg *sync.WaitGroup) {
|
||||||
|
|
||||||
|
for i, inst := range *section {
|
||||||
|
tsec.instructions[i].start = time.Now()
|
||||||
|
var err error
|
||||||
|
if dryRun {
|
||||||
|
_, err = inst.DryRun(ctx)
|
||||||
|
} else {
|
||||||
|
_, err = inst.Exec(ctx)
|
||||||
|
|
||||||
|
}
|
||||||
|
tsec.instructions[i].stop = time.Now()
|
||||||
|
tsec.instructions[i].stopped = true
|
||||||
|
tsec.instructions[i].err = err
|
||||||
|
refresher <- true
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func status(table []tableSection, refresher <-chan bool, wg *sync.WaitGroup) {
|
||||||
|
|
||||||
|
for opened := true; true; _, opened = <-refresher {
|
||||||
|
|
||||||
|
// 1. clean screen
|
||||||
|
fmt.Printf("\033[H\033[2J")
|
||||||
|
|
||||||
|
// 2. for each section
|
||||||
|
for _, sec := range table {
|
||||||
|
|
||||||
|
fmt.Printf("\n[ %s ]\n", sec.name)
|
||||||
|
|
||||||
|
// 3. for each instruction
|
||||||
|
for i, inst := range sec.instructions {
|
||||||
|
|
||||||
|
if !inst.stopped {
|
||||||
|
clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(0, inst.name)))
|
||||||
|
fmt.Printf("%s\n", clifmt.Color(33, "processing"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if inst.err != nil {
|
||||||
|
clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(0, inst.name)))
|
||||||
|
fmt.Printf("%s\n", clifmt.Color(31, inst.err.Error()))
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(34, inst.name)))
|
||||||
|
fmt.Printf("%ss\n", clifmt.Color(32, fmt.Sprintf("%.2f", inst.stop.Sub(inst.start).Seconds())))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opened {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Done()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package buildfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/instruction"
|
"git.xdrm.io/go/nix-amer/internal/instruction"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ func TestNullContext(t *testing.T) {
|
||||||
}
|
}
|
||||||
func TestIgnoreCommentsAndEmptyLines(t *testing.T) {
|
func TestIgnoreCommentsAndEmptyLines(t *testing.T) {
|
||||||
ctx, _ := instruction.CreateContext("apt-get", "")
|
ctx, _ := instruction.CreateContext("apt-get", "")
|
||||||
buffer := bytes.NewBufferString("[ some comment ]\n\n \t \n\t \t\n[ other comment after empty lines ]")
|
buffer := bytes.NewBufferString("# some comment\n;other comment\n \t \n\t \t\n; other comment after empty lines")
|
||||||
|
|
||||||
r, err := NewReader(ctx, buffer)
|
r, err := NewReader(ctx, buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -33,24 +33,24 @@ func TestInstructionSyntax(t *testing.T) {
|
||||||
Line int
|
Line int
|
||||||
Err error
|
Err error
|
||||||
}{
|
}{
|
||||||
{"install args\ndelete args\n", 0, nil},
|
{"[pre]\ninstall args\ndelete args\n", 1, nil},
|
||||||
{" install args\ndelete args\n", 0, nil},
|
{"[pre]\n install args\ndelete args\n", 1, nil},
|
||||||
{"\tinstall args\ndelete args\n", 0, nil},
|
{"[pre]\n\tinstall args\ndelete args\n", 1, nil},
|
||||||
{" \t install args\ndelete args\n", 0, nil},
|
{"[pre]\n \t install args\ndelete args\n", 1, nil},
|
||||||
{" \t install args\ndelete args\n", 0, nil},
|
{"[pre]\n \t install args\ndelete args\n", 1, nil},
|
||||||
|
|
||||||
{"cmd args\ncmd args\n", 1, instruction.ErrUnknownInstruction},
|
{"[pre]\ncmd args\ncmd args\n", 2, instruction.ErrUnknownInstruction},
|
||||||
{"install args\ncmd args\n", 2, instruction.ErrUnknownInstruction},
|
{"[pre]\ninstall args\ncmd args\n", 3, instruction.ErrUnknownInstruction},
|
||||||
{" cmd args\ncmd args\n", 1, instruction.ErrUnknownInstruction},
|
{"[pre]\n cmd args\ncmd args\n", 2, instruction.ErrUnknownInstruction},
|
||||||
{"\tcmd args\ncmd args\n", 1, instruction.ErrUnknownInstruction},
|
{"[pre]\n\tcmd args\ncmd args\n", 2, instruction.ErrUnknownInstruction},
|
||||||
{" \t cmd args\ncmd args\n", 1, instruction.ErrUnknownInstruction},
|
{"[pre]\n \t cmd args\ncmd args\n", 2, instruction.ErrUnknownInstruction},
|
||||||
{" \t cmd args\ncmd args\n", 1, instruction.ErrUnknownInstruction},
|
{"[pre]\n \t cmd args\ncmd args\n", 2, instruction.ErrUnknownInstruction},
|
||||||
|
|
||||||
{"cmd args\ncmd\n", 1, instruction.ErrUnknownInstruction},
|
{"[pre]\ncmd args\ncmd\n", 2, instruction.ErrUnknownInstruction},
|
||||||
{"install\ncmd args\n", 1, instruction.ErrInvalidSyntax},
|
{"[pre]\ninstall\ncmd args\n", 2, instruction.ErrInvalidSyntax},
|
||||||
|
|
||||||
{"install args\n cmd args\n", 2, instruction.ErrUnknownInstruction},
|
{"[pre]\ninstall args\n cmd args\n", 3, instruction.ErrUnknownInstruction},
|
||||||
{"install args\ncmd\n", 2, instruction.ErrInvalidSyntax},
|
{"[pre]\ninstall args\ncmd\n", 3, instruction.ErrInvalidSyntax},
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, _ := instruction.CreateContext("apt-get", "")
|
ctx, _ := instruction.CreateContext("apt-get", "")
|
||||||
|
@ -64,7 +64,11 @@ func TestInstructionSyntax(t *testing.T) {
|
||||||
// no error expected
|
// no error expected
|
||||||
if test.Err == nil {
|
if test.Err == nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lineerr := err.(LineError)
|
lineerr, ok := err.(LineError)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("[%d] expect error to be of type <LineError>", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
t.Errorf("[%d] expect no error, got <%s>", i, lineerr.Err)
|
t.Errorf("[%d] expect no error, got <%s>", i, lineerr.Err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -92,7 +92,7 @@ func DisplaySize(s string) int {
|
||||||
matches = reDots.FindAllString(s, -1)
|
matches = reDots.FindAllString(s, -1)
|
||||||
for _, m := range matches {
|
for _, m := range matches {
|
||||||
size -= len(m)
|
size -= len(m)
|
||||||
size += 1
|
size++
|
||||||
}
|
}
|
||||||
|
|
||||||
return size
|
return size
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
package cnf
|
||||||
|
|
||||||
|
import (
|
||||||
|
lib "git.xdrm.io/go/nix-amer/internal/cnf/parser/bash"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bash struct {
|
||||||
|
data *lib.File
|
||||||
|
parsed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFrom implements io.ReaderFrom
|
||||||
|
func (d *bash) ReadFrom(_reader io.Reader) (int64, error) {
|
||||||
|
|
||||||
|
d.data = new(lib.File)
|
||||||
|
|
||||||
|
// 1. get bash decoder
|
||||||
|
decoder := lib.NewDecoder(_reader)
|
||||||
|
err := decoder.Decode(d.data)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.parsed = true
|
||||||
|
return 0, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo implements io.WriterTo
|
||||||
|
func (d *bash) WriteTo(_writer io.Writer) (int64, error) {
|
||||||
|
encoder := lib.NewEncoder(_writer)
|
||||||
|
return 0, encoder.Encode(d.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// browse returns the target of a dot-separated path (as an interface{} chain where the last is the target if found)
|
||||||
|
// if <create> is true, create if does not exist
|
||||||
|
func (d *bash) browse(_path string, create ...bool) (*lib.Line, bool) {
|
||||||
|
|
||||||
|
mustCreate := len(create) > 0 && create[0]
|
||||||
|
|
||||||
|
// 1. extract path
|
||||||
|
path := strings.Split(_path, ".")
|
||||||
|
field := path[len(path)-1]
|
||||||
|
|
||||||
|
// 2. nothing
|
||||||
|
if len(path) < 1 {
|
||||||
|
return &lib.Line{}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. iterate over path / nested fields
|
||||||
|
for _, line := range d.data.Lines {
|
||||||
|
if line.Type == lib.ASSIGNMENT && len(line.Components) > 1 && line.Components[1] == field {
|
||||||
|
return line, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. create assignment
|
||||||
|
if mustCreate {
|
||||||
|
assignment := &lib.Line{
|
||||||
|
Type: lib.ASSIGNMENT,
|
||||||
|
Components: []string{"", field, "", ""},
|
||||||
|
}
|
||||||
|
d.data.Lines = append(d.data.Lines, assignment)
|
||||||
|
return assignment, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the value of a dot-separated path, and if it exists
|
||||||
|
func (d *bash) Get(_path string) (string, bool) {
|
||||||
|
|
||||||
|
// 1. browse path
|
||||||
|
last, found := d.browse(_path, true)
|
||||||
|
if !found {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Get last field
|
||||||
|
return last.Components[2], true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the value of a dot-separated path, and creates it if not found
|
||||||
|
func (d *bash) Set(_path, _value string) bool {
|
||||||
|
|
||||||
|
// 1. browse path
|
||||||
|
last, found := d.browse(_path, true)
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Set value
|
||||||
|
last.Components[2] = _value
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,220 @@
|
||||||
|
package cnf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBashGet(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
raw string
|
||||||
|
key string
|
||||||
|
}{
|
||||||
|
{"key=value;\n", "key"},
|
||||||
|
{" \t key=value;\n", "key"},
|
||||||
|
{"key=value; #comment\n", "key"},
|
||||||
|
{"\t key=value; #comment\n", "key"},
|
||||||
|
{"key=value; # comment\n", "key"},
|
||||||
|
{"\t \tkey=value; # comment\n", "key"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
|
||||||
|
parser := new(bash)
|
||||||
|
reader := bytes.NewBufferString(test.raw)
|
||||||
|
|
||||||
|
// try to extract value
|
||||||
|
_, err := parser.ReadFrom(reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] parse error: %s", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract value
|
||||||
|
value, found := parser.Get(test.key)
|
||||||
|
if !found {
|
||||||
|
t.Errorf("[%d] expected a result, got none", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check value
|
||||||
|
if value != "value" {
|
||||||
|
t.Errorf("[%d] expected 'value' got '%s'", i, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBashSetPathExists(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
raw string
|
||||||
|
key string
|
||||||
|
value string
|
||||||
|
}{
|
||||||
|
|
||||||
|
{"key=value;\n", "key", "newvalue"},
|
||||||
|
{" \t key=value;\n", "key", "newvalue"},
|
||||||
|
{"key=value; #comment\n", "key", "newvalue"},
|
||||||
|
{"\t key=value; #comment\n", "key", "newvalue"},
|
||||||
|
{"key=value; # comment\n", "key", "newvalue"},
|
||||||
|
{"\t \tkey=value; # comment\n", "key", "newvalue"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
|
||||||
|
parser := new(bash)
|
||||||
|
reader := bytes.NewBufferString(test.raw)
|
||||||
|
|
||||||
|
// try to extract value
|
||||||
|
_, err := parser.ReadFrom(reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] parse error: %s", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// update value
|
||||||
|
if !parser.Set(test.key, test.value) {
|
||||||
|
t.Errorf("[%d] cannot set '%s' to '%s'", i, test.key, test.value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check new value
|
||||||
|
value, found := parser.Get(test.key)
|
||||||
|
if !found {
|
||||||
|
t.Errorf("[%d] expected a result, got none", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check value
|
||||||
|
if value != test.value {
|
||||||
|
t.Errorf("[%d] expected '%s' got '%s'", i, test.value, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBashSetCreatePath(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
raw string
|
||||||
|
key string
|
||||||
|
ignore string // path to field that must be present after transformation
|
||||||
|
value string
|
||||||
|
}{
|
||||||
|
{"ignore=xxx;\n", "key", "ignore", "newvalue"},
|
||||||
|
{"unknown-line\nignore=xxx;\n", "key", "ignore", "newvalue"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
|
||||||
|
parser := new(bash)
|
||||||
|
reader := bytes.NewBufferString(test.raw)
|
||||||
|
|
||||||
|
// try to extract value
|
||||||
|
_, err := parser.ReadFrom(reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] parse error: %s", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// update value
|
||||||
|
if !parser.Set(test.key, test.value) {
|
||||||
|
t.Errorf("[%d] cannot set '%s' to '%s'", i, test.key, test.value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check new value
|
||||||
|
value, found := parser.Get(test.key)
|
||||||
|
if !found {
|
||||||
|
t.Errorf("[%d] expected a result, got none", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check value
|
||||||
|
if value != test.value {
|
||||||
|
t.Errorf("[%d] expected '%s' got '%s'", i, test.value, value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that ignore field is still there
|
||||||
|
value, found = parser.Get(test.ignore)
|
||||||
|
if !found {
|
||||||
|
t.Errorf("[%d] expected ignore field, got none", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check value
|
||||||
|
if value != "xxx" {
|
||||||
|
t.Errorf("[%d] expected ignore value to be '%s' got '%s'", i, "xxx", value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBashSetCreateEncode(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
raw string
|
||||||
|
key string
|
||||||
|
value string
|
||||||
|
encoded string
|
||||||
|
}{
|
||||||
|
{"ignore=xxx;\n", "key", `"newvalue"`, "ignore=xxx;\nkey=\"newvalue\";\n"},
|
||||||
|
{"#!/bin/bash\n\nfunc(){\n\techo \"something\";\n}\nignore=xxx;\n", "key", `"newvalue"`, "#!/bin/bash\nfunc(){\n\techo \"something\";\n}\nignore=xxx;\nkey=\"newvalue\";\n"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
|
||||||
|
parser := new(bash)
|
||||||
|
r, w := bytes.NewBufferString(test.raw), new(bytes.Buffer)
|
||||||
|
|
||||||
|
// try to extract value
|
||||||
|
_, err := parser.ReadFrom(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] parse error: %s", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// update value
|
||||||
|
if !parser.Set(test.key, test.value) {
|
||||||
|
t.Errorf("[%d] cannot set '%s' to '%s'", i, test.key, test.value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check new value
|
||||||
|
value, found := parser.Get(test.key)
|
||||||
|
if !found {
|
||||||
|
t.Errorf("[%d] expected a result, got none", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check value
|
||||||
|
if value != test.value {
|
||||||
|
t.Errorf("[%d] expected '%s' got '%s'", i, test.value, value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeToBuffer
|
||||||
|
_, err = parser.WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] unexpected write error <%s>", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded := w.String()
|
||||||
|
|
||||||
|
// check value
|
||||||
|
if encoded != test.encoded {
|
||||||
|
t.Errorf("[%d] wrong encoded value \n-=-=-= HAVE =-=-=-\n%s\n-=-=-= WANT =-=-=-\n%s\n-=-=-=-=-=\n", i, escape(test.encoded), escape(encoded))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to write
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,8 +16,15 @@ type ini struct {
|
||||||
// ReadFrom implements io.ReaderFrom
|
// ReadFrom implements io.ReaderFrom
|
||||||
func (d *ini) ReadFrom(_reader io.Reader) (int64, error) {
|
func (d *ini) ReadFrom(_reader io.Reader) (int64, error) {
|
||||||
|
|
||||||
|
// disallow "key: value"
|
||||||
|
// when trying to find out the format from content
|
||||||
|
// and avoids conflicts with YAML
|
||||||
|
opts := lib.LoadOptions{
|
||||||
|
KeyValueDelimiters: "=",
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := lib.LoadSources(opts, ioutil.NopCloser(_reader))
|
||||||
// 1. get json decoder
|
// 1. get json decoder
|
||||||
file, err := lib.Load(ioutil.NopCloser(_reader))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ package cnf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
@ -14,51 +12,45 @@ var ErrUnknownExtension = errors.New("unknown extension format")
|
||||||
// ErrUnknownFormat is raised when the format cannot be guessed from the content of the file
|
// ErrUnknownFormat is raised when the format cannot be guessed from the content of the file
|
||||||
var ErrUnknownFormat = errors.New("cannot infer format from content")
|
var ErrUnknownFormat = errors.New("cannot infer format from content")
|
||||||
|
|
||||||
|
// ErrFileNotExist is raised when required file does not exist
|
||||||
|
var ErrFileNotExist = errors.New("cannot find file")
|
||||||
|
|
||||||
// Load the current file and create the configuration format accordingly
|
// Load the current file and create the configuration format accordingly
|
||||||
func Load(path string) (ConfigurationFormat, error) {
|
func Load(path string) (ConfigurationFormat, error) {
|
||||||
|
|
||||||
var confFormat ConfigurationFormat
|
|
||||||
|
|
||||||
// 1. check file
|
// 1. check file
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
return nil, fmt.Errorf("cannot find file '%s'", path)
|
return nil, ErrFileNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Try to load from extension
|
// 2. Try to load from extension
|
||||||
extension := filepath.Ext(path)
|
extension := filepath.Ext(path)
|
||||||
if len(extension) > 0 {
|
if len(extension) > 0 {
|
||||||
|
|
||||||
confFormat = loadFromExtension(extension)
|
if confFormat := loadFromExtension(extension); confFormat != nil {
|
||||||
if confFormat == nil {
|
|
||||||
return nil, ErrUnknownExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
// open file
|
// open file
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// parse
|
// parse
|
||||||
_, err = confFormat.ReadFrom(file)
|
_, err = confFormat.ReadFrom(file)
|
||||||
|
file.Close()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return confFormat, nil
|
return confFormat, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// return nil, fmt.Errorf("cannot parse file as '%s' | %s", extension, err)
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. open file
|
// 4. Try to guess from the content
|
||||||
file, err := os.Open(path)
|
confFormat, err := loadFromContent(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// 4. Try to guess from the content
|
|
||||||
confFormat = loadFromContent(file)
|
|
||||||
if confFormat == nil {
|
if confFormat == nil {
|
||||||
return nil, ErrUnknownFormat
|
return nil, ErrUnknownFormat
|
||||||
}
|
}
|
||||||
|
@ -78,19 +70,29 @@ func loadFromExtension(ext string) ConfigurationFormat {
|
||||||
return new(yaml)
|
return new(yaml)
|
||||||
case ".nginx":
|
case ".nginx":
|
||||||
return new(nginx)
|
return new(nginx)
|
||||||
|
case ".sh":
|
||||||
|
return new(bash)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadFromContent(file io.Reader) ConfigurationFormat {
|
func loadFromContent(path string) (ConfigurationFormat, error) {
|
||||||
|
|
||||||
// extensions ordered by unicity of the language's syntax
|
// 3. open file
|
||||||
extensions := []string{".json", ".yaml", ".nginx", ".ini"}
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// extensions ordered by strictness of the language's syntax
|
||||||
|
extensions := []string{".json", ".nginx", ".ini", ".yaml", ".sh"}
|
||||||
|
|
||||||
// try to load each available extension
|
// try to load each available extension
|
||||||
for _, ext := range extensions {
|
for _, ext := range extensions {
|
||||||
|
file.Seek(0, 0)
|
||||||
|
|
||||||
// load parser
|
// load parser
|
||||||
c := loadFromExtension(ext)
|
c := loadFromExtension(ext)
|
||||||
|
@ -101,10 +103,10 @@ func loadFromContent(file io.Reader) ConfigurationFormat {
|
||||||
// parse
|
// parse
|
||||||
_, err := c.ReadFrom(file)
|
_, err := c.ReadFrom(file)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return c
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil, ErrUnknownFormat
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,250 @@
|
||||||
|
package cnf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadFileNotExist(t *testing.T) {
|
||||||
|
|
||||||
|
_, err := Load("/tmp/not-existing/file")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
if err != ErrFileNotExist {
|
||||||
|
t.Fatalf("expected error <%s>, got <%s>", ErrFileNotExist, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestLoadFromExtensionWithContentMatch(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
File string
|
||||||
|
Format ConfigurationFormat
|
||||||
|
Content string
|
||||||
|
Field string
|
||||||
|
}{
|
||||||
|
|
||||||
|
// key = value
|
||||||
|
{"/tmp/load-test.json", new(json), `{ "key": "value" }`, "key"},
|
||||||
|
{"/tmp/load-test.nginx", new(nginx), "key value;", "key"},
|
||||||
|
{"/tmp/load-test.ini", new(ini), `key = value`, "key"},
|
||||||
|
{"/tmp/load-test.yaml", new(yaml), "key: value", "key"},
|
||||||
|
|
||||||
|
// parent.key = value
|
||||||
|
{"/tmp/load-test.json", new(json), `{ "parent": { "key": "value" } }`, "parent.key"},
|
||||||
|
{"/tmp/load-test.nginx", new(nginx), "parent {\nkey value;\n}", "parent.key"},
|
||||||
|
{"/tmp/load-test.ini", new(ini), "[parent]\nkey = value", "parent.key"},
|
||||||
|
{"/tmp/load-test.yaml", new(yaml), "parent:\n key: value", "parent.key"},
|
||||||
|
|
||||||
|
// comments
|
||||||
|
// {"/tmp/load-test.json", new(json), "{ \"parent\": { \"key\": \"value\" } }", "parent.key"},
|
||||||
|
{"/tmp/load-test.nginx", new(nginx), ";comment\nparent {\nkey value;\n}", "parent.key"},
|
||||||
|
{"/tmp/load-test.nginx", new(nginx), "#comment\nparent {\nkey value;\n}", "parent.key"},
|
||||||
|
{"/tmp/load-test.ini", new(ini), ";comment\n[parent]\nkey = value", "parent.key"},
|
||||||
|
{"/tmp/load-test.ini", new(ini), "#comment\n[parent]\nkey = value", "parent.key"},
|
||||||
|
{"/tmp/load-test.yaml", new(yaml), "#comment\nparent:\n key: value", "parent.key"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
|
||||||
|
f, err := os.Create(test.File)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] cannot create file '%s'", i, test.File)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.Write([]byte(test.Content))
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
format, err := Load(test.File)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] unexpected error <%s>", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if format == nil {
|
||||||
|
t.Errorf("[%d] expected format not to be null", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check type
|
||||||
|
have, want := fmt.Sprintf("%T", format), fmt.Sprintf("%T", test.Format)
|
||||||
|
if have != want {
|
||||||
|
t.Errorf("[%d] expected format <%s>, got <%s>", i, want, have)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to get value
|
||||||
|
val, found := format.Get(test.Field)
|
||||||
|
if !found {
|
||||||
|
t.Errorf("[%d] expected to find '%s'", i, test.Field)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != "value" {
|
||||||
|
t.Errorf("[%d] expected value '%s', got '%s'", i, "value", val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
os.RemoveAll(test.File)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadFromExtensionWithContentNotMatch(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
File string
|
||||||
|
Format ConfigurationFormat
|
||||||
|
Content string
|
||||||
|
Field string
|
||||||
|
}{
|
||||||
|
|
||||||
|
// [JSON] key = value
|
||||||
|
{"/tmp/load-test.json", new(json), `{ "key": "value" }`, "key"},
|
||||||
|
{"/tmp/load-test.nginx", new(json), `{ "key": "value" }`, "key"},
|
||||||
|
{"/tmp/load-test.ini", new(json), `{ "key": "value" }`, "key"},
|
||||||
|
// {"/tmp/load-test.yaml", new(json), `{ "key": "value" }`, "key"},
|
||||||
|
|
||||||
|
// [NGINX] key = value
|
||||||
|
{"/tmp/load-test.json", new(nginx), "key value;", "key"},
|
||||||
|
{"/tmp/load-test.nginx", new(nginx), "key value;", "key"},
|
||||||
|
{"/tmp/load-test.ini", new(nginx), "key value;", "key"},
|
||||||
|
// {"/tmp/load-test.yaml", new(nginx), "key value;", "key"},
|
||||||
|
|
||||||
|
// [INI] key = value
|
||||||
|
{"/tmp/load-test.json", new(ini), `key = value`, "key"},
|
||||||
|
{"/tmp/load-test.nginx", new(ini), `key = value`, "key"},
|
||||||
|
{"/tmp/load-test.ini", new(ini), `key = value`, "key"},
|
||||||
|
// {"/tmp/load-test.yaml", new(ini), `key = value`, "key"},
|
||||||
|
|
||||||
|
// [YAML] key = value
|
||||||
|
{"/tmp/load-test.json", new(yaml), "key: value", "key"},
|
||||||
|
{"/tmp/load-test.nginx", new(yaml), "key: value", "key"},
|
||||||
|
{"/tmp/load-test.yaml", new(yaml), "key: value", "key"},
|
||||||
|
{"/tmp/load-test.yaml", new(yaml), "key: value", "key"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
|
||||||
|
f, err := os.Create(test.File)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] cannot create file '%s'", i, test.File)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.Write([]byte(test.Content))
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
format, err := Load(test.File)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] unexpected error <%s>", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if format == nil {
|
||||||
|
t.Errorf("[%d] expected format not to be null", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check type
|
||||||
|
have, want := fmt.Sprintf("%T", format), fmt.Sprintf("%T", test.Format)
|
||||||
|
if have != want {
|
||||||
|
t.Errorf("[%d] expected format <%s>, got <%s>", i, want, have)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to get value
|
||||||
|
val, found := format.Get(test.Field)
|
||||||
|
if !found {
|
||||||
|
t.Errorf("[%d] expected to find '%s'", i, test.Field)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != "value" {
|
||||||
|
t.Errorf("[%d] expected value '%s', got '%s'", i, "value", val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
os.RemoveAll(test.File)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestLoadContent(t *testing.T) {
|
||||||
|
|
||||||
|
file := "/tmp/no-extension-file"
|
||||||
|
defer os.RemoveAll(file)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Format ConfigurationFormat
|
||||||
|
Content string
|
||||||
|
Field string
|
||||||
|
}{
|
||||||
|
|
||||||
|
// key = value
|
||||||
|
{new(json), `{ "key": "value" }`, "key"},
|
||||||
|
{new(nginx), "key value;", "key"},
|
||||||
|
{new(ini), `key = value`, "key"},
|
||||||
|
{new(yaml), "key: value", "key"},
|
||||||
|
|
||||||
|
// parent.key = value
|
||||||
|
{new(json), `{ "parent": { "key": "value" } }`, "parent.key"},
|
||||||
|
{new(nginx), "parent {\nkey value;\n}", "parent.key"},
|
||||||
|
{new(ini), "[parent]\nkey = value", "parent.key"},
|
||||||
|
{new(yaml), "parent:\n key: value", "parent.key"},
|
||||||
|
|
||||||
|
// comments
|
||||||
|
{new(json), "{ \"parent\": { \"key\": \"value\" } }", "parent.key"},
|
||||||
|
{new(nginx), ";comment\nparent {\nkey value;\n}", "parent.key"},
|
||||||
|
{new(nginx), "#comment\nparent {\nkey value;\n}", "parent.key"},
|
||||||
|
{new(ini), ";comment\n[parent]\nkey = value", "parent.key"},
|
||||||
|
{new(ini), "#comment\n[parent]\nkey = value", "parent.key"},
|
||||||
|
{new(yaml), "#comment\nparent:\n key: value", "parent.key"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
|
||||||
|
os.RemoveAll(file)
|
||||||
|
f, err := os.Create(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] cannot create file '%s'", i, file)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.Write([]byte(test.Content))
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
format, err := Load(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] unexpected error <%s>", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if format == nil {
|
||||||
|
t.Errorf("[%d] expected format not to be null", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check type
|
||||||
|
have, want := fmt.Sprintf("%T", format), fmt.Sprintf("%T", test.Format)
|
||||||
|
if have != want {
|
||||||
|
t.Errorf("[%d] expected format <%s>, got <%s>", i, want, have)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to get value
|
||||||
|
val, found := format.Get(test.Field)
|
||||||
|
if !found {
|
||||||
|
t.Errorf("[%d] expected to find '%s'", i, test.Field)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != "value" {
|
||||||
|
t.Errorf("[%d] expected value '%s', got '%s'", i, "value", val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package cnf
|
package cnf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
lib "github.com/xdrm-brackets/nix-amer/internal/cnf/parser/nginx"
|
lib "git.xdrm.io/go/nix-amer/internal/cnf/parser/nginx"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -78,6 +78,7 @@ func (d *nginx) browse(_path string, create ...bool) (*lib.Line, bool) {
|
||||||
Lines: make([]*lib.Line, 0),
|
Lines: make([]*lib.Line, 0),
|
||||||
}
|
}
|
||||||
current.Lines = append(current.Lines, sec)
|
current.Lines = append(current.Lines, sec)
|
||||||
|
current = sec
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,7 @@ func TestNginxSetCreatePath(t *testing.T) {
|
||||||
{"ignore xxx;\n", "key", "ignore", "newvalue"},
|
{"ignore xxx;\n", "key", "ignore", "newvalue"},
|
||||||
{"ignore xxx;\nsection {\n\tkey value;\n}\n", "section.key", "ignore", "newvalue"},
|
{"ignore xxx;\nsection {\n\tkey value;\n}\n", "section.key", "ignore", "newvalue"},
|
||||||
{"section {\n\tkey value;\n\tignore xxx;\n}\n", "section.key", "section.ignore", "newvalue"},
|
{"section {\n\tkey value;\n\tignore xxx;\n}\n", "section.key", "section.ignore", "newvalue"},
|
||||||
|
{"ignoresec {\n\tignore xxx;\n}\n\nsection {\n}\n", "section.key", "ignoresec.ignore", "newvalue"},
|
||||||
{"ignoresec {\n\tignore xxx;\n}\n\nsection {\n\tkey value;\n}\n", "section.key", "ignoresec.ignore", "newvalue"},
|
{"ignoresec {\n\tignore xxx;\n}\n\nsection {\n\tkey value;\n}\n", "section.key", "ignoresec.ignore", "newvalue"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,3 +152,103 @@ func TestNginxSetCreatePath(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNginxSetCreateEncode(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
raw string
|
||||||
|
key string
|
||||||
|
value string
|
||||||
|
encoded string
|
||||||
|
}{
|
||||||
|
{"ignore xxx;\n", "key", "newvalue", "ignore\t\txxx;\nkey\t\tnewvalue;\n"},
|
||||||
|
{"ignore xxx;\nsection {\n\tkey value;\n}\n", "section.key", "newvalue", "ignore\t\txxx;\nsection {\n\tkey\t\tnewvalue;\n}\n\n"},
|
||||||
|
{"section {\n\tkey value;\n\tignore xxx;\n}\n", "section.key", "newvalue", "section {\n\tkey\t\tnewvalue;\n\tignore\t\txxx;\n}\n\n"},
|
||||||
|
{"ignoresec {\n\tignore xxx;\n}\n\nsection {\n}\n", "section.key", "newvalue", "ignoresec {\n\tignore\t\txxx;\n}\n\nsection {\n\tkey\t\tnewvalue;\n}\n\n"},
|
||||||
|
{"ignoresec {\n\tignore xxx;\n}\n\nsection {\n\tkey value;\n}\n", "section.key", "newvalue", "ignoresec {\n\tignore\t\txxx;\n}\n\nsection {\n\tkey\t\tnewvalue;\n}\n\n"},
|
||||||
|
|
||||||
|
{
|
||||||
|
"; comment 1\n\nk1 v1;\n#comment 2\nsec1 {\n}\nsec2 {\nignore xxx;\n sec3 {\nignore2 yyy;\n}\n\n}\n\n",
|
||||||
|
"key",
|
||||||
|
"newvalue",
|
||||||
|
"; comment 1\nk1\t\tv1;\n#comment 2\nsec1 {\n}\n\nsec2 {\n\tignore\t\txxx;\n\tsec3 {\n\t\tignore2\t\tyyy;\n\t}\n\n}\n\nkey\t\tnewvalue;\n"},
|
||||||
|
{
|
||||||
|
"; comment 1\n\nk1 v1;\n#comment 2\nsec1 {\n}\nsec2 {\nignore xxx;\n sec3 {\nignore2 yyy;\n}\n\n}\n\n",
|
||||||
|
"section.key",
|
||||||
|
"newvalue",
|
||||||
|
"; comment 1\nk1\t\tv1;\n#comment 2\nsec1 {\n}\n\nsec2 {\n\tignore\t\txxx;\n\tsec3 {\n\t\tignore2\t\tyyy;\n\t}\n\n}\n\nsection {\n\tkey\t\tnewvalue;\n}\n\n"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
|
||||||
|
parser := new(nginx)
|
||||||
|
r, w := bytes.NewBufferString(test.raw), new(bytes.Buffer)
|
||||||
|
|
||||||
|
// try to extract value
|
||||||
|
_, err := parser.ReadFrom(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] parse error: %s", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// update value
|
||||||
|
if !parser.Set(test.key, test.value) {
|
||||||
|
t.Errorf("[%d] cannot set '%s' to '%s'", i, test.key, test.value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check new value
|
||||||
|
value, found := parser.Get(test.key)
|
||||||
|
if !found {
|
||||||
|
t.Errorf("[%d] expected a result, got none", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check value
|
||||||
|
if value != test.value {
|
||||||
|
t.Errorf("[%d] expected '%s' got '%s'", i, test.value, value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeToBuffer
|
||||||
|
_, err = parser.WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] unexpected write error <%s>", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded := w.String()
|
||||||
|
|
||||||
|
// check value
|
||||||
|
if encoded != test.encoded {
|
||||||
|
t.Errorf("[%d] wrong encoded value \n-=-=-= HAVE =-=-=-\n%s\n-=-=-= WANT =-=-=-\n%s\n-=-=-=-=-=\n", i, escape(test.encoded), escape(encoded))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to write
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func escape(in string) string {
|
||||||
|
|
||||||
|
out := make([]rune, 0)
|
||||||
|
|
||||||
|
for _, char := range in {
|
||||||
|
if char == '\\' {
|
||||||
|
out = append(out, []rune("\\\\")...)
|
||||||
|
} else if char == '\n' {
|
||||||
|
out = append(out, []rune("\\n")...)
|
||||||
|
} else if char == '\r' {
|
||||||
|
out = append(out, []rune("\\r")...)
|
||||||
|
} else if char == '\t' {
|
||||||
|
out = append(out, []rune("\\t")...)
|
||||||
|
} else if char == '\033' {
|
||||||
|
out = append(out, []rune("\\033")...)
|
||||||
|
} else {
|
||||||
|
out = append(out, char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package bash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File represents a bash file
|
||||||
|
type File struct {
|
||||||
|
Lines []*Line
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder implements parser.T
|
||||||
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
|
return &Decoder{reader: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder implements parser.T
|
||||||
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
|
return &Encoder{writer: w}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package bash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decoder implements parser.Decoder
|
||||||
|
type Decoder struct{ reader io.Reader }
|
||||||
|
|
||||||
|
// Decode is the main function of the parser.Decoder interface
|
||||||
|
func (d *Decoder) Decode(v interface{}) error {
|
||||||
|
|
||||||
|
// check 'v'
|
||||||
|
if v == nil {
|
||||||
|
return ErrNullReceiver
|
||||||
|
}
|
||||||
|
vcast, ok := v.(*File)
|
||||||
|
if !ok {
|
||||||
|
return ErrInvalidReceiver
|
||||||
|
}
|
||||||
|
vcast.Lines = make([]*Line, 0)
|
||||||
|
|
||||||
|
r := bufio.NewReader(d.reader)
|
||||||
|
n := -1 // line number
|
||||||
|
|
||||||
|
// regexes
|
||||||
|
reAssign := regexp.MustCompile(`(?m)^(\s*)([A-Za-z0-9_]+)=([^;]+);?\s*(#.+)?$`)
|
||||||
|
eof := false
|
||||||
|
|
||||||
|
for {
|
||||||
|
n++
|
||||||
|
if eof {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &Line{Type: ANY}
|
||||||
|
|
||||||
|
// 1. read line
|
||||||
|
line, err := r.ReadString('\n')
|
||||||
|
if err == io.EOF {
|
||||||
|
if len(line) > 0 {
|
||||||
|
eof = true
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return &LineError{n, err}
|
||||||
|
}
|
||||||
|
line = strings.Trim(line, "\r\n")
|
||||||
|
|
||||||
|
// 2. ignore empty
|
||||||
|
if len(strings.Trim(line, " \t\r\n")) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. assignment
|
||||||
|
match := reAssign.FindStringSubmatch(line)
|
||||||
|
if match != nil {
|
||||||
|
l.Type = ASSIGNMENT
|
||||||
|
l.Components = match[1:]
|
||||||
|
} else {
|
||||||
|
l.Components = []string{line}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. add to file
|
||||||
|
vcast.Lines = append(vcast.Lines, l)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package bash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encoder implements parser.Encoder
|
||||||
|
type Encoder struct {
|
||||||
|
writer io.Writer
|
||||||
|
prefix []byte
|
||||||
|
indent []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode is the main function of the parser.Encoder interface
|
||||||
|
func (e *Encoder) Encode(v interface{}) error {
|
||||||
|
|
||||||
|
// check 'v'
|
||||||
|
vcast, ok := v.(*File)
|
||||||
|
if !ok {
|
||||||
|
return ErrInvalidReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty config
|
||||||
|
if len(vcast.Lines) < 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range vcast.Lines {
|
||||||
|
|
||||||
|
// line representation
|
||||||
|
repr := ""
|
||||||
|
|
||||||
|
// ASSIGNMENT
|
||||||
|
if line.Type == ASSIGNMENT {
|
||||||
|
repr = fmt.Sprintf("%s%s=%s;", line.Components[0], line.Components[1], line.Components[2])
|
||||||
|
|
||||||
|
// optional comment
|
||||||
|
if len(line.Components[3]) > 0 {
|
||||||
|
repr += fmt.Sprintf(" %s", line.Components[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANY
|
||||||
|
} else {
|
||||||
|
repr = strings.Join(line.Components, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
repr += "\n"
|
||||||
|
|
||||||
|
_, err := e.writer.Write([]byte(repr))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package bash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.xdrm.io/go/nix-amer/internal/clifmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNullReceiver is raised when a null receiver is provided
|
||||||
|
var ErrNullReceiver = fmt.Errorf("receiver must not be null")
|
||||||
|
|
||||||
|
// ErrInvalidReceiver is raised when an invalid receiver is provided
|
||||||
|
var ErrInvalidReceiver = fmt.Errorf("receiver must be compatible with *File")
|
||||||
|
|
||||||
|
// LineError wraps errors with a line index
|
||||||
|
type LineError struct {
|
||||||
|
Line int
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements Error
|
||||||
|
func (le LineError) Error() string {
|
||||||
|
return fmt.Sprintf(":%d %s", le.Line, clifmt.Color(31, le.Err.Error()))
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package bash
|
||||||
|
|
||||||
|
// LineType enumerates available line types
|
||||||
|
type LineType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ANY is the default line type (all except assignments)
|
||||||
|
ANY LineType = iota
|
||||||
|
// ASSIGNMENT line
|
||||||
|
ASSIGNMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
// Line represents a meaningful line
|
||||||
|
type Line struct {
|
||||||
|
// Type of line
|
||||||
|
Type LineType
|
||||||
|
|
||||||
|
// Components of the line
|
||||||
|
//
|
||||||
|
// When Type = ASSIGNMENT :
|
||||||
|
// [0] = indentation (spaces and tabs)
|
||||||
|
// [1] = variable name
|
||||||
|
// [2] = variable value
|
||||||
|
// [3] = comment (optional)
|
||||||
|
Components []string
|
||||||
|
}
|
|
@ -153,5 +153,4 @@ func (d *Decoder) Decode(v interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,12 +201,12 @@ func TestReceiverAndSyntaxErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var readerError = errors.New("error")
|
var errReader = errors.New("error")
|
||||||
|
|
||||||
type defectiveReader struct{}
|
type defectiveReader struct{}
|
||||||
|
|
||||||
func (d defectiveReader) Read(buf []byte) (int, error) {
|
func (d defectiveReader) Read(buf []byte) (int, error) {
|
||||||
return 0, readerError
|
return 0, errReader
|
||||||
}
|
}
|
||||||
func TestReadErrors(t *testing.T) {
|
func TestReadErrors(t *testing.T) {
|
||||||
|
|
||||||
|
@ -221,8 +221,8 @@ func TestReadErrors(t *testing.T) {
|
||||||
t.Fatalf("expected error")
|
t.Fatalf("expected error")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err.Error() != (&LineError{0, readerError}).Error() {
|
if err.Error() != (&LineError{0, errReader}).Error() {
|
||||||
t.Fatalf("expected error <%s>, got <%s>", &LineError{0, readerError}, err)
|
t.Fatalf("expected error <%s>, got <%s>", &LineError{0, errReader}, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,12 +167,12 @@ func TestDefaultIndent(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var writerError = errors.New("error")
|
var errWriter = errors.New("error")
|
||||||
|
|
||||||
type defectiveWriter struct{}
|
type defectiveWriter struct{}
|
||||||
|
|
||||||
func (d defectiveWriter) Write(buf []byte) (int, error) {
|
func (d defectiveWriter) Write(buf []byte) (int, error) {
|
||||||
return 0, writerError
|
return 0, errWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteError(t *testing.T) {
|
func TestWriteError(t *testing.T) {
|
||||||
|
@ -191,8 +191,8 @@ func TestWriteError(t *testing.T) {
|
||||||
|
|
||||||
// encode back to writer
|
// encode back to writer
|
||||||
encoder := NewEncoder(w)
|
encoder := NewEncoder(w)
|
||||||
if err := encoder.Encode(receiver); err != writerError {
|
if err := encoder.Encode(receiver); err != errWriter {
|
||||||
t.Fatalf("expected error <%s>, got <%s>", writerError, err)
|
t.Fatalf("expected error <%s>, got <%s>", errWriter, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package nginx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/clifmt"
|
"git.xdrm.io/go/nix-amer/internal/clifmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNullReceiver is raised when a null receiver is provided
|
// ErrNullReceiver is raised when a null receiver is provided
|
||||||
|
|
|
@ -39,3 +39,7 @@ func (d alias) Exec(ctx ExecutionContext) ([]byte, error) {
|
||||||
ctx.Alias[d.Name] = d.Value
|
ctx.Alias[d.Name] = d.Value
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d alias) DryRun(ctx ExecutionContext) ([]byte, error) {
|
||||||
|
return d.Exec(ctx)
|
||||||
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@ package instruction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
"git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/pkg"
|
"git.xdrm.io/go/nix-amer/internal/pkg"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/ser"
|
"git.xdrm.io/go/nix-amer/internal/ser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// T is the instruction common interface
|
// T is the instruction common interface
|
||||||
|
@ -15,6 +15,8 @@ type T interface {
|
||||||
Build(string) error
|
Build(string) error
|
||||||
// Exec the given instruction
|
// Exec the given instruction
|
||||||
Exec(ExecutionContext) ([]byte, error)
|
Exec(ExecutionContext) ([]byte, error)
|
||||||
|
// DryRun checks the success of the given instruction without actually running it (non-destructive)
|
||||||
|
DryRun(ExecutionContext) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecutionContext contains system-specific drivers to manage the host
|
// ExecutionContext contains system-specific drivers to manage the host
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package instruction
|
package instruction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -17,7 +16,7 @@ func (d *copy) Raw() string { return strings.Join([]string{"copy", d.raw}, " ")
|
||||||
func (d *copy) Build(_args string) error {
|
func (d *copy) Build(_args string) error {
|
||||||
|
|
||||||
// 1. extract action (sub command)
|
// 1. extract action (sub command)
|
||||||
split := strings.Split(_args, " ")
|
split := strings.Fields(_args)
|
||||||
|
|
||||||
// 2. check syntax
|
// 2. check syntax
|
||||||
if len(split) != 2 {
|
if len(split) != 2 {
|
||||||
|
@ -34,14 +33,52 @@ func (d copy) Exec(ctx ExecutionContext) ([]byte, error) {
|
||||||
|
|
||||||
// 1. fail if source file not found
|
// 1. fail if source file not found
|
||||||
if _, err := os.Stat(d.Src); os.IsNotExist(err) {
|
if _, err := os.Stat(d.Src); os.IsNotExist(err) {
|
||||||
return nil, fmt.Errorf("cannot find script '%s'", d.Src)
|
return nil, &FileError{"cannot find file", d.Src, err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. execute script
|
// 2. execute script
|
||||||
if err := ctx.Executor.Command("cp", d.Src, d.Dst).Run(); err != nil {
|
if err := ctx.Executor.Command("cp", "-r", d.Src, d.Dst).Run(); err != nil {
|
||||||
return nil, fmt.Errorf("cannot copy '%s' to '%s' | %s", d.Src, d.Dst, err)
|
return nil, &FileError{"cannot copy to", d.Dst, err}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d copy) DryRun(ctx ExecutionContext) ([]byte, error) {
|
||||||
|
|
||||||
|
// 1. fail if source file not found
|
||||||
|
if _, err := os.Stat(d.Src); os.IsNotExist(err) {
|
||||||
|
return nil, &FileError{"cannot find file", d.Src, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. if destination to create : try to create (then remove)
|
||||||
|
fi, err := os.Stat(d.Dst)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
|
||||||
|
file, err2 := os.OpenFile(d.Dst, os.O_APPEND|os.O_WRONLY|os.O_CREATE, os.FileMode(0777))
|
||||||
|
if err2 != nil {
|
||||||
|
return nil, &FileError{"cannot copy to", d.Dst, err2}
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
if err2 := os.Remove(d.Dst); err2 != nil {
|
||||||
|
return nil, &FileError{"cannot remove dry-run file", d.Dst, err2}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
} else if fi != nil && fi.IsDir() {
|
||||||
|
return nil, nil // no error if dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. if destination exists : check write permission
|
||||||
|
file, err := os.OpenFile(d.Dst, os.O_APPEND|os.O_WRONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &FileError{"cannot copy to", d.Dst, err}
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,308 @@
|
||||||
|
package instruction
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCopyInvalidSyntax(t *testing.T) {
|
||||||
|
tests := []string{
|
||||||
|
"one-arg",
|
||||||
|
"src dst extra-arg",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
inst := new(copy)
|
||||||
|
err := inst.Build(test)
|
||||||
|
if err != ErrInvalidSyntax {
|
||||||
|
t.Errorf("[%d] expected error <%s>, got <%s>", i, ErrInvalidSyntax, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestCopyBuildArgs(t *testing.T) {
|
||||||
|
tests := []string{
|
||||||
|
"source destination",
|
||||||
|
"\tsource destination",
|
||||||
|
" source destination",
|
||||||
|
"source\t destination",
|
||||||
|
"source destination",
|
||||||
|
"source \tdestination",
|
||||||
|
"source destination",
|
||||||
|
"source destination\t",
|
||||||
|
"source\t\tdestination\t",
|
||||||
|
"source \t\t destination\t",
|
||||||
|
"source\t \tdestination\t",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
inst := new(copy)
|
||||||
|
err := inst.Build(test)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] unexpected error <%s>", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if inst.Src != "source" {
|
||||||
|
t.Errorf("[%d] expected 'source', got '%s'", i, inst.Src)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if inst.Dst != "destination" {
|
||||||
|
t.Errorf("[%d] expected 'source', got '%s'", i, inst.Dst)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopySourceNotExist(t *testing.T) {
|
||||||
|
defer os.RemoveAll("/tmp/destination")
|
||||||
|
raw := "/tmp/source /tmp/destination"
|
||||||
|
|
||||||
|
ctx, err := CreateContext("apt-get", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot create context")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
inst := new(copy)
|
||||||
|
err := inst.Build(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] unexpected error <%s>", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
_, err = inst.Exec(*ctx)
|
||||||
|
} else {
|
||||||
|
_, err = inst.DryRun(*ctx)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("[%d] expected error", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
ce, ok := err.(*FileError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("[%d] expected error of type <*FileError>", i)
|
||||||
|
}
|
||||||
|
if ce.Reason != "cannot find file" || ce.File != "/tmp/source" {
|
||||||
|
t.Fatalf("[%d] expected error <%s '%s'> got <%s '%s'>", i, "cannot find file", "/tmp/source", ce.Reason, ce.File)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopySourceIsDir(t *testing.T) {
|
||||||
|
src, dst := "/tmp/sourcedir", "/tmp/destinationdir"
|
||||||
|
raw := fmt.Sprintf("%s %s", src, dst)
|
||||||
|
defer os.RemoveAll(src)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
ctx, err := CreateContext("apt-get", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot create context")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
// 1. create directory
|
||||||
|
os.RemoveAll(src)
|
||||||
|
if err := os.MkdirAll(src, os.FileMode(0777)); err != nil {
|
||||||
|
t.Fatalf("[%d] cannot create test directory | %s", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inst := new(copy)
|
||||||
|
err := inst.Build(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] unexpected error <%s>", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
_, err = inst.Exec(*ctx)
|
||||||
|
} else {
|
||||||
|
_, err = inst.DryRun(*ctx)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] unexpected error <%s>", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestCopyInvalidDestination(t *testing.T) {
|
||||||
|
src, dst := "/tmp/source", "/tmp/missing-directory/invalid-destination"
|
||||||
|
raw := fmt.Sprintf("%s %s", src, dst)
|
||||||
|
defer os.RemoveAll(src)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
ctx, err := CreateContext("apt-get", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot create context")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
|
||||||
|
os.RemoveAll(src)
|
||||||
|
// 1. create directory
|
||||||
|
if err := os.MkdirAll(src, os.FileMode(0777)); err != nil {
|
||||||
|
t.Fatalf("[%d] cannot create test directory | %s", i, err)
|
||||||
|
}
|
||||||
|
inst := new(copy)
|
||||||
|
err := inst.Build(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] unexpected error <%s>", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
_, err = inst.Exec(*ctx)
|
||||||
|
} else {
|
||||||
|
_, err = inst.DryRun(*ctx)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("[%d] expected error", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
ce, ok := err.(*FileError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("[%d] expected error of type <*FileError>", i)
|
||||||
|
}
|
||||||
|
if ce.Reason != "cannot copy to" || ce.File != dst {
|
||||||
|
t.Fatalf("[%d] expected error <%s '%s'> got <%s '%s'>", i, "cannot copy to", dst, ce.Reason, ce.File)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestCopySourceIsFile(t *testing.T) {
|
||||||
|
src, dst := "/tmp/source", "/tmp/destination-file"
|
||||||
|
raw := fmt.Sprintf("%s %s", src, dst)
|
||||||
|
defer os.RemoveAll(src)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
ctx, err := CreateContext("apt-get", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot create context")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
|
||||||
|
os.RemoveAll(src)
|
||||||
|
|
||||||
|
// 1. create directory
|
||||||
|
fd, err := os.Create(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] cannot create test file | %s", i, err)
|
||||||
|
}
|
||||||
|
fd.Close()
|
||||||
|
|
||||||
|
inst := new(copy)
|
||||||
|
err = inst.Build(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] unexpected error <%s>", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
_, err = inst.Exec(*ctx)
|
||||||
|
} else {
|
||||||
|
_, err = inst.DryRun(*ctx)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] unexpected error <%s>", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopySourceIsFileDestinationExists(t *testing.T) {
|
||||||
|
src, dst := "/tmp/source", "/tmp/destination-file"
|
||||||
|
raw := fmt.Sprintf("%s %s", src, dst)
|
||||||
|
defer os.RemoveAll(src)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
ctx, err := CreateContext("apt-get", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot create context")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
|
||||||
|
os.RemoveAll(src)
|
||||||
|
os.RemoveAll(dst)
|
||||||
|
|
||||||
|
// 1. create source
|
||||||
|
fd, err := os.Create(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] cannot create test file | %s", i, err)
|
||||||
|
}
|
||||||
|
fd.Close()
|
||||||
|
// 1. create destination
|
||||||
|
fd, err = os.Create(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] cannot create test file | %s", i, err)
|
||||||
|
}
|
||||||
|
fd.Close()
|
||||||
|
|
||||||
|
inst := new(copy)
|
||||||
|
err = inst.Build(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] unexpected error <%s>", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
_, err = inst.Exec(*ctx)
|
||||||
|
} else {
|
||||||
|
_, err = inst.DryRun(*ctx)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] unexpected error <%s>", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestCopyCannotWriteDestination(t *testing.T) {
|
||||||
|
src, dst := "/tmp/source", "/tmp/destination-perm"
|
||||||
|
raw := fmt.Sprintf("%s %s", src, dst)
|
||||||
|
defer os.RemoveAll(src)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
ctx, err := CreateContext("apt-get", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot create context")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
|
||||||
|
os.RemoveAll(src)
|
||||||
|
if err := os.RemoveAll(dst); err != nil {
|
||||||
|
t.Fatalf("[%d] cannot remove destination file\n", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. create source
|
||||||
|
fd, err := os.Create(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] cannot create test file | %s", i, err)
|
||||||
|
}
|
||||||
|
fd.Close()
|
||||||
|
// 1. create destination
|
||||||
|
fd, err = os.Create(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] cannot create test file | %s", i, err)
|
||||||
|
}
|
||||||
|
err = fd.Chmod(os.FileMode(0555))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] cannot set permissions | %s", i, err)
|
||||||
|
}
|
||||||
|
fd.Close()
|
||||||
|
|
||||||
|
inst := new(copy)
|
||||||
|
err = inst.Build(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] unexpected error <%s>", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
_, err = inst.Exec(*ctx)
|
||||||
|
} else {
|
||||||
|
_, err = inst.DryRun(*ctx)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] unexpected error <%s>", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,3 +33,7 @@ func (d delete) Exec(ctx ExecutionContext) ([]byte, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d delete) DryRun(ctx ExecutionContext) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,29 @@
|
||||||
package instruction
|
package instruction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrInvalidAlias is raised when encountering an invalid token in an alias name
|
// ErrInvalidAlias is raised when encountering an invalid token in an alias name
|
||||||
var ErrInvalidAlias = errors.New("invalid alias name (contains '/')")
|
var ErrInvalidAlias = fmt.Errorf("invalid alias name (contains '/')")
|
||||||
|
|
||||||
// ErrInvalidSyntax is raised when encountering an invalid token
|
// ErrInvalidSyntax is raised when encountering an invalid token
|
||||||
var ErrInvalidSyntax = errors.New("invalid instruction format")
|
var ErrInvalidSyntax = fmt.Errorf("invalid instruction format")
|
||||||
|
|
||||||
// ErrUnknownInstruction is raised when encountering an unknown instruction
|
// ErrUnknownInstruction is raised when encountering an unknown instruction
|
||||||
// it can mean that you're not using the right version or that you've misspelled it
|
// it can mean that you're not using the right version or that you've misspelled it
|
||||||
var ErrUnknownInstruction = errors.New("unknown instruction")
|
var ErrUnknownInstruction = fmt.Errorf("unknown instruction")
|
||||||
|
|
||||||
|
// FileError is used for file-specific errors
|
||||||
|
type FileError struct {
|
||||||
|
Reason string
|
||||||
|
File string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ce FileError) Error() string {
|
||||||
|
if ce.Err == nil {
|
||||||
|
return fmt.Sprintf("%s '%s'", ce.Reason, ce.File)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s '%s' | %s", ce.Reason, ce.File, ce.Err)
|
||||||
|
}
|
||||||
|
|
|
@ -33,3 +33,7 @@ func (d install) Exec(ctx ExecutionContext) ([]byte, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d install) DryRun(ctx ExecutionContext) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
|
@ -40,3 +40,21 @@ func (d run) Exec(ctx ExecutionContext) ([]byte, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
func (d run) DryRun(ctx ExecutionContext) ([]byte, error) {
|
||||||
|
|
||||||
|
// 1. get file / alias
|
||||||
|
path := d.raw
|
||||||
|
if !strings.Contains(path, "/") {
|
||||||
|
if p, exists := ctx.Alias[path]; exists {
|
||||||
|
path = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. fail if file not found
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("cannot find script '%s'", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -60,3 +60,17 @@ func (d service) Exec(ctx ExecutionContext) ([]byte, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
func (d service) DryRun(ctx ExecutionContext) ([]byte, error) {
|
||||||
|
|
||||||
|
// fail if a service does not exist
|
||||||
|
for _, service := range d.Services {
|
||||||
|
|
||||||
|
if err := ctx.ServiceManager.Exec("status", service); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot find service '%s' | %s", service, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -2,8 +2,9 @@ package instruction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/cnf"
|
"git.xdrm.io/go/nix-amer/internal/cnf"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -94,3 +95,57 @@ func (d set) Exec(ctx ExecutionContext) ([]byte, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
func (d set) DryRun(ctx ExecutionContext) ([]byte, error) {
|
||||||
|
|
||||||
|
// 1. get file / alias
|
||||||
|
path := d.File
|
||||||
|
if !strings.Contains(path, "/") {
|
||||||
|
if p, exists := ctx.Alias[path]; exists {
|
||||||
|
path = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. fail if file not found
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("cannot find file '%s'", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. try to load format
|
||||||
|
format, err := cnf.Load(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. try to update value
|
||||||
|
if !format.Set(d.Path, d.Value) {
|
||||||
|
return nil, ErrCannotSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. fail on missing write permission
|
||||||
|
file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, os.FileMode(0775))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot update '%s' | %s", path, err)
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
// 6. create non-destructive dry-run folder
|
||||||
|
dryRunFolder := "/tmp/dry-run"
|
||||||
|
if err := os.MkdirAll(dryRunFolder, os.FileMode(0777)); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot create dry-run folder | %s", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. create updated file inside .dry-run
|
||||||
|
tmpout := filepath.Join(dryRunFolder, strings.Replace(path, "/", "-", -1))
|
||||||
|
file, err = os.Create(tmpout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot create dry-run file '%s' | %s", tmpout, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
if _, err = format.WriteTo(file); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot write dry-run file '%s' | %s", tmpout, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import "github.com/xdrm-brackets/nix-amer/internal/exec"
|
import "git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
|
|
||||||
type apk struct{ exec exec.Executor }
|
type apk struct{ exec exec.Executor }
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
"git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
type aptGet struct{ exec exec.Executor }
|
type aptGet struct{ exec exec.Executor }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
"git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultManager if not empty is the default package-manager to use when missing
|
// DefaultManager if not empty is the default package-manager to use when missing
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import "github.com/xdrm-brackets/nix-amer/internal/exec"
|
import "git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
|
|
||||||
type dnf struct{ exec exec.Executor }
|
type dnf struct{ exec exec.Executor }
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import "github.com/xdrm-brackets/nix-amer/internal/exec"
|
import "git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
|
|
||||||
type eopkg struct{ exec exec.Executor }
|
type eopkg struct{ exec exec.Executor }
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
"git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrUnknownManager is raised when the asked manager does not exist
|
// ErrUnknownManager is raised when the asked manager does not exist
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
"git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import "github.com/xdrm-brackets/nix-amer/internal/exec"
|
import "git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
|
|
||||||
type pacman struct{ exec exec.Executor }
|
type pacman struct{ exec exec.Executor }
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import "github.com/xdrm-brackets/nix-amer/internal/exec"
|
import "git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
|
|
||||||
type yum struct{ exec exec.Executor }
|
type yum struct{ exec exec.Executor }
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ser
|
package ser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
"git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultManager if not empty is the default service-manager to use when missing
|
// DefaultManager if not empty is the default service-manager to use when missing
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
"git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrUnknownManager is raised when the asked manager does not exist
|
// ErrUnknownManager is raised when the asked manager does not exist
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package ser
|
package ser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
"git.xdrm.io/go/nix-amer/internal/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
type systemd struct{ exec exec.Executor }
|
type systemd struct{ exec exec.Executor }
|
||||||
|
|
||||||
// available actions
|
// available actions
|
||||||
var actions = []string{"enable", "disable", "start", "stop", "reload", "restart"}
|
var actions = []string{"enable", "disable", "status", "start", "stop", "reload", "restart"}
|
||||||
|
|
||||||
func (d *systemd) SetExecutor(_exec exec.Executor) {
|
func (d *systemd) SetExecutor(_exec exec.Executor) {
|
||||||
d.exec = _exec
|
d.exec = _exec
|
||||||
|
|
20
main.go
20
main.go
|
@ -2,8 +2,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/buildfile"
|
"git.xdrm.io/go/nix-amer/internal/buildfile"
|
||||||
"github.com/xdrm-brackets/nix-amer/internal/clifmt"
|
"git.xdrm.io/go/nix-amer/internal/clifmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -30,7 +30,12 @@ func main() {
|
||||||
// 2. parse buildfile
|
// 2. parse buildfile
|
||||||
instructions, err := buildfile.NewReader(ctx, bfreader)
|
instructions, err := buildfile.NewReader(ctx, bfreader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s%s\n", bf, err)
|
if _, ok := err.(buildfile.LineError); ok {
|
||||||
|
fmt.Printf("line error\n")
|
||||||
|
fmt.Printf("%s%s\n", bf, err.Error())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s\n", clifmt.Warn(err.Error()))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,13 +43,10 @@ func main() {
|
||||||
fmt.Printf("%s\n", clifmt.Color(32, "valid"))
|
fmt.Printf("%s\n", clifmt.Color(32, "valid"))
|
||||||
|
|
||||||
// stop here if dry run
|
// stop here if dry run
|
||||||
if dryRun {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Execute
|
// 3. Execute
|
||||||
fmt.Printf("------\n")
|
fmt.Printf("------\n")
|
||||||
err = instructions.Execute()
|
err = instructions.Execute(dryRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s\n", clifmt.Warn(err.Error()))
|
fmt.Printf("%s\n", clifmt.Warn(err.Error()))
|
||||||
return
|
return
|
||||||
|
@ -53,4 +55,8 @@ func main() {
|
||||||
clifmt.Align("finished in")
|
clifmt.Align("finished in")
|
||||||
fmt.Printf("%ss\n", clifmt.Color(32, fmt.Sprintf("%.2f", time.Now().Sub(start).Seconds())))
|
fmt.Printf("%ss\n", clifmt.Color(32, fmt.Sprintf("%.2f", time.Now().Sub(start).Seconds())))
|
||||||
|
|
||||||
|
if dryRun {
|
||||||
|
fmt.Printf("\n%s %s\n", clifmt.Info("updated configurations are inside"), clifmt.Color(32, "/tmp/dry-run"))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue