Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Packet Workflow #3

Merged
merged 150 commits into from
Mar 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
150 commits
Select commit Hold shift + click to select a range
88d24ea
vendored dependencies
gauravgahlot Oct 18, 2019
83324e1
gRPC server, client and CLI
gauravgahlot Oct 19, 2019
f154004
database script and client
gauravgahlot Oct 19, 2019
209e04f
docker and build
gauravgahlot Oct 19, 2019
e9892e1
entrypoint for server
gauravgahlot Oct 19, 2019
d3cbd07
rover template
gauravgahlot Oct 28, 2019
dd6f7a6
rover target
gauravgahlot Oct 28, 2019
ef31a5f
rover workflow
gauravgahlot Oct 28, 2019
be2547a
updated dependencies
gauravgahlot Oct 31, 2019
40ef3b3
secure private registry
gauravgahlot Oct 31, 2019
1db9150
workflow executor and worker
gauravgahlot Oct 31, 2019
56fad24
Added support of creating of WFState Table, Event Table and WfWorker …
parauliya Oct 31, 2019
f5486c4
Incorporated the review comments
parauliya Nov 1, 2019
55ca530
Fix the get worker ID by IP address which was not working earliar
parauliya Nov 1, 2019
73a4d79
Added validation for unique task name and unique action name in a task.
parauliya Nov 1, 2019
956425b
Added code for update workflow_state and workflow_event table in db
parauliya Nov 2, 2019
163e801
Create db function to run end to end workflow in respect of db changes
parauliya Nov 3, 2019
177d36f
pull docker image for actions
gauravgahlot Nov 4, 2019
d02c021
Added support of creating of WFState Table, Event Table and WfWorker …
parauliya Oct 31, 2019
d8c96d8
Incorporated the review comments
parauliya Nov 1, 2019
62ee88e
Fix the get worker ID by IP address which was not working earliar
parauliya Nov 1, 2019
b3c37f5
Added validation for unique task name and unique action name in a task.
parauliya Nov 1, 2019
b103eb2
Added code for update workflow_state and workflow_event table in db
parauliya Nov 2, 2019
0c3e2be
Create db function to run end to end workflow in respect of db changes
parauliya Nov 3, 2019
f69b7b8
Remove the commented code
parauliya Nov 4, 2019
2a8c298
Merge branch 'workflow-child' of https://github.com/packethost/rover …
parauliya Nov 4, 2019
d89b958
Merge pull request #5 from packethost/workflow-child
gauravgahlot Nov 4, 2019
1fad471
code change for doing all the db operation in one transaction to avoi…
parauliya Nov 5, 2019
70cab1f
docker registry now uses htpasswd
gauravgahlot Nov 6, 2019
e05b968
worker to pull images after authentication
gauravgahlot Nov 6, 2019
4d83672
let client and server negotiate API version
gauravgahlot Nov 6, 2019
6482276
Added "rover workflow state" cli
parauliya Nov 6, 2019
250230b
docker files cleanup
gauravgahlot Nov 7, 2019
76531d2
returning errors instead of os.exit(1)
gauravgahlot Nov 7, 2019
8bbd50c
using retries from env
gauravgahlot Nov 7, 2019
ee1d5a3
returing elapsed time for an action
gauravgahlot Nov 7, 2019
4158884
Merge branch 'workflow' of github.com:packethost/rover into workflow
parauliya Nov 7, 2019
821f6d7
Added "rover workflow events" CLI
parauliya Nov 7, 2019
c94e585
Fixed some minor comments
parauliya Nov 7, 2019
23b5f9b
Fixed an issue in worker
Nov 11, 2019
955b4a1
using action command if present
Nov 12, 2019
b658edc
Added support for ContainerWait and some other stuff
Nov 12, 2019
e9eb5a8
Merge branch 'workflow' of github.com:packethost/rover into workflow
Nov 12, 2019
8c1ad3b
Reverted docker-compose file
Nov 12, 2019
fa7cf74
Resume the workflow if worker reboots in between
Nov 13, 2019
e69d084
Fixed db related problem and some comments
Nov 13, 2019
07e33ad
Fix the rover workflow delete command issue
Nov 15, 2019
621f218
Fix one more issue found in workflow delete cli
Nov 15, 2019
211e4a4
Code for removing action containers
Nov 15, 2019
8310331
genrating certs separately
Nov 18, 2019
8432dbc
update rover ports and sample template
Nov 18, 2019
d07550d
Fix the rover workflow delete command issue
Nov 15, 2019
afa477b
Fix one more issue found in workflow delete cli
Nov 15, 2019
06c5450
Code for removing action containers
Nov 15, 2019
0866e9f
Added code for action timeout
Nov 19, 2019
afdc666
Merge branch 'workflow' of github.com:packethost/rover into workflow
Nov 19, 2019
7a5f405
Fixed some review comments
Nov 20, 2019
aca143f
Fixed one comment
Nov 20, 2019
c59a333
Added importing structure across proto files
Nov 21, 2019
9a646b3
Add cacher server part in rover
Nov 21, 2019
dd6d616
Merging cacher cli under rover
Nov 21, 2019
0826c95
Merge pull request #6 from packethost/workflow_cacher_merge
parauliya Nov 26, 2019
441224d
Added some validation for template
Nov 26, 2019
98c2513
Merge branch 'workflow' of github.com:packethost/rover into workflow
Nov 26, 2019
8458e1f
using fluentbit, elasticsearch and kibana
Nov 27, 2019
9bae780
Merged rover.proto into workflow.proto
Dec 10, 2019
6f97177
Merge pull request #7 from packethost/rover_proto_remove
parauliya Dec 11, 2019
2804e82
remved proto-gen from Makefile
Dec 12, 2019
d1b163a
Rename the init() function in rover client
Dec 20, 2019
d2035f1
Merge branch 'workflow' of github.com:packethost/rover into workflow
Dec 20, 2019
c53beca
changes in proto for workflow data
Dec 30, 2019
2698f32
server and db changes to handle workflow ephemeral data
Jan 2, 2020
1019244
sharing workflow data among workers
Jan 2, 2020
f90bfae
Added tracing of modification in ephemeral data
Jan 6, 2020
b275271
Fixed one minor error
Jan 7, 2020
ce9562a
Revert "Fixed one minor error"
Jan 7, 2020
37e3568
Fixed issues
Jan 7, 2020
db81df3
passing action name and worker ID
Jan 7, 2020
b1e71fa
sharing workflow data among workers
Jan 2, 2020
e912388
Added tracing of modification in ephemeral data
Jan 6, 2020
c7d879e
saving workflow data details as metadata
Jan 10, 2020
1639339
CLI to get workflow data
Jan 13, 2020
2220479
Merge conflicts with latest workflow branch
Jan 13, 2020
ad6b3d6
few fixes and changes to get workflow metadata
Jan 13, 2020
5cbef2f
Adding basic test framework for rover for local testing
Jan 14, 2020
89e5d45
Merge pull request #10 from packethost/rover_test_framework
parauliya Jan 14, 2020
4652a97
automate the manual steps for default run of test framework
Jan 14, 2020
f443931
Fixed one minor issue
Jan 14, 2020
a0a8caa
fixed few trivial review comments
Jan 14, 2020
fff52bc
Merge pull request #11 from packethost/rover_test_framework
parauliya Jan 14, 2020
dfe3ede
changes to limit workflow data versions in DB
Jan 16, 2020
c586718
adding tags for JSON encoding
Jan 16, 2020
19fb263
changes latest workflow data version number
Jan 16, 2020
8ec0d84
Added ephemeral data check and restructure the code
Jan 16, 2020
7953c96
changes to get latest workflow data version number
Jan 16, 2020
dda7490
Added timeout test case in test framework
Jan 17, 2020
99f1cbc
Merge branch 'workflow' of github.com:packethost/rover into workflow
Jan 17, 2020
31b5a61
Couple of Fixes in worker and addition of logrus as logger
Jan 31, 2020
f05e6a3
Changes in test framework
Jan 31, 2020
dccdaca
An action now supports volumes and environment variables
Jan 21, 2020
590c5a4
Changes in rover to cross compile rover
Jan 31, 2020
2fe4d2d
run action container in privileged mode
Feb 13, 2020
5d15bb8
Changes in the logging method in worker to avoid duplicate logging
Feb 21, 2020
183d866
changes in tes framework for looging
Feb 25, 2020
c1c0b27
A task in a workflow can now have volumes.
Feb 12, 2020
0dd8769
Added Env support at Task level
Mar 3, 2020
93ebb81
initial commit for rover docs
Mar 4, 2020
01f7e1d
workflow architecture
Mar 4, 2020
1c3112f
common errors
Mar 5, 2020
4dc7eba
writing a workflow
Mar 5, 2020
91fff40
fixed links in README
Mar 5, 2020
7d6dd83
restructuring
Mar 5, 2020
3d84f01
Removed vendored dependencies.
Jan 22, 2020
11bca36
Using go-modules
Jan 22, 2020
eb69203
Changes in rover to cross compile rover
Jan 31, 2020
2fe29a2
Fixed few bugs
Mar 5, 2020
25ce98d
Remove merge conflicts
Mar 5, 2020
e51fab3
Merge pull request #15 from packethost/rover_arm_compile
parauliya Mar 5, 2020
33008dd
fixed one issue in env support at task level
Mar 5, 2020
7a83179
Changes in test framework
Jan 31, 2020
459ce12
changes in tes framework for looging
Feb 25, 2020
e6fb724
fixed some issue in the test framework
Mar 5, 2020
df2403b
Conflict resolved
Mar 5, 2020
e16472b
Merge pull request #14 from packethost/rover_test_framwork
parauliya Mar 5, 2020
43f0f86
fixed an issue with volume support for tasks
Mar 6, 2020
a716727
writing a workflow, updated
Mar 5, 2020
4230063
first-good-workflow
Mar 11, 2020
f1cfd4f
using relative link
Mar 11, 2020
dfc308e
components updated
Mar 11, 2020
d88fb91
updated README and provisioner data
Mar 11, 2020
1e22687
rover CLI reference
Mar 11, 2020
668ed38
fixed typos
Mar 11, 2020
210d2be
quick fixes in CLI
Mar 11, 2020
edc1a5b
renaming `rover` -> `tinkerbell`
Mar 18, 2020
66ed9a2
updated README
Mar 18, 2020
621341b
cacher removed
Mar 18, 2020
d785302
Merge pull request #20 from packethost/documentation
nathangoulding Mar 18, 2020
eb7b179
Changes everything from rover to tinkerbell
Mar 18, 2020
1769b47
Update components.md
Mar 18, 2020
1cd33d9
Merge branch 'workflow' of github.com:packethost/tinkerbell into rove…
Mar 18, 2020
05bff50
update README file
Mar 18, 2020
bb59924
Merge pull request #22 from packethost/rover_to_tinkerbell
nathangoulding Mar 18, 2020
161b12d
minor change to build the image of tinkerbell server
Mar 18, 2020
2d9469b
Adding binaries so that build is not required
Mar 18, 2020
0fa2915
Revert "Adding binaries so that build is not required"
Mar 18, 2020
48fb288
Changed the env variables name
Mar 18, 2020
b59e4c4
updated architecture image
gauravgahlot Mar 18, 2020
577e806
updated ephemeral data image
gauravgahlot Mar 18, 2020
3ed3e5b
updated .gitignore
gauravgahlot Mar 18, 2020
5fc6973
Merge branch 'master' into workflow
gauravgahlot Mar 18, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*
!/tls/
!tinkerbell-server
!entrypoint.sh
!deploy/migrate
!deploy/docker-entrypoint-initdb.d/tinkerbell-init.sql
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
tinkerbell-server
**/tinkerbell-cli
**/tinkerbell-worker
bin/
certs/

11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM alpine:3.7

ENTRYPOINT [ "/tinkerbell" ]
EXPOSE 42113
EXPOSE 42114

RUN apk add --no-cache --update --upgrade ca-certificates postgresql-client
RUN apk add --no-cache --update --upgrade --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing cfssl
COPY deploy/migrate /migrate
COPY deploy/docker-entrypoint-initdb.d/tinkerbell-init.sql /init.sql
COPY tinkerbell-server /tinkerbell
29 changes: 29 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
server := tinkerbell-server
cli := tinkerbell-cli
worker := tinkerbell-worker
binaries := ${server} ${cli} ${worker}
all: ${binaries}

.PHONY: server ${binaries} cli worker test
server: ${server}
cli: ${cli}
worker : ${worker}

${bindir}:
mkdir -p $@/

${server}:
CGO_ENABLED=0 go build -o $@ .

${cli}:
CGO_ENABLED=0 go build -o ./cmd/tinkerbell/$@ ./cmd/tinkerbell

${worker}:
CGO_ENABLED=0 go build -o ./worker/$@ ./worker/

run: ${binaries}
docker-compose up -d --build db
docker-compose up --build tinkerbell boots
test:
go clean -testcache
go test ./test -v
42 changes: 40 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
# tinkerbell
# Tinkerbell [https://tinkerbell.org](https://tinkerbell.org)

At the highest level `tinkerbell` is the service responsible for handling the workflows. It comprises of a server and a CLI, which communicate over gRPC. The CLI is used to create a workflow along with its building blocks, i.e., template and target.


# Packet Workflow

A Packet Workflow is an open-source microservice that’s responsible for handling flexible, bare metal
provisioning workflows, that is...
- standalone and does not need the Packet API to function
- contains `Boots`, `Tinkerbell`, `Osie`, and workers
- can bootstrap any remote worker using `Boots + Osie`
- can run any set of actions as Docker container runtimes
- receive, manipulate, and save runtime data


## Content

- [Setup](docs/setup.md)
- [Components](docs/components.md)
- [Boots](docs/components.md#boots)
- [Osie](docs/components.md#osie)
- [Tinkerbell](docs/components.md#tinkerbell)
- [Hegel](docs/components.md#hegel)
- [Database](docs/components.md#database)
- [Image Registry](docs/components.md#registry)
- [Elasticsearch](docs/components.md#elastic)
- [Fluent Bit](docs/components.md#cacher)
- [Kibana](docs/components.md#kibana)
- [Architecture](docs/architecture.md)
- [Example: First Good Workflow](docs/first-good-workflow.md)
- [Concepts](docs/concepts.md)
- [Template](docs/concepts.md#template)
- [Target](docs/concepts.md#target)
- [Provisioner](docs/concepts.md#provisioner)
- [Worker](docs/concepts.md#worker)
- [Ephemeral Data](docs/concepts.md#ephemeral-data)
- [Writing a Workflow](docs/writing-workflow.md)
- [Tinkerbell CLI Reference](docs/cli/README.md)
- [Troubleshooting](docs/troubleshoot.md)

https://tinkerbell.org
80 changes: 80 additions & 0 deletions client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package client

import (
"crypto/x509"
"io/ioutil"
"log"
"net/http"
"os"

"github.com/packethost/tinkerbell/protos/hardware"
"github.com/packethost/tinkerbell/protos/target"
"github.com/packethost/tinkerbell/protos/template"
"github.com/packethost/tinkerbell/protos/workflow"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

// gRPC clients
var (
TemplateClient template.TemplateClient
TargetClient target.TargetClient
WorkflowClient workflow.WorkflowSvcClient
HardwareClient hardware.HardwareServiceClient
)

// GetConnection returns a gRPC client connection
func GetConnection() (*grpc.ClientConn, error) {
certURL := os.Getenv("TINKERBELL_CERT_URL")
if certURL == "" {
return nil, errors.New("undefined TINKERBELL_CERT_URL")
}
resp, err := http.Get(certURL)
if err != nil {
return nil, errors.Wrap(err, "fetch cert")
}
defer resp.Body.Close()

certs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "read cert")
}

cp := x509.NewCertPool()
ok := cp.AppendCertsFromPEM(certs)
if !ok {
return nil, errors.Wrap(err, "parse cert")
}

grpcAuthority := os.Getenv("TINKERBELL_GRPC_AUTHORITY")
if grpcAuthority == "" {
return nil, errors.New("undefined TINKERBELL_GRPC_AUTHORITY")
}
creds := credentials.NewClientTLSFromCert(cp, "")
conn, err := grpc.Dial(grpcAuthority, grpc.WithTransportCredentials(creds))
if err != nil {
return nil, errors.Wrap(err, "connect to tinkerbell server")
}
return conn, nil
}

// Setup : create a connection to server
func Setup() {
conn, err := GetConnection()
if err != nil {
log.Fatal(err)
}
TemplateClient = template.NewTemplateClient(conn)
TargetClient = target.NewTargetClient(conn)
WorkflowClient = workflow.NewWorkflowSvcClient(conn)
HardwareClient = hardware.NewHardwareServiceClient(conn)
}

func NewTinkerbellClient() (hardware.HardwareServiceClient, error) {
conn, err := GetConnection()
if err != nil {
log.Fatal(err)
}
return hardware.NewHardwareServiceClient(conn), nil
}
Empty file removed cmd/rover/.gitkeep
Empty file.
7 changes: 7 additions & 0 deletions cmd/tinkerbell/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM alpine:3.7

CMD sleep 60d

RUN apk add --no-cache --update --upgrade ca-certificates
COPY tinkerbell-cli /bin/tinkerbell
COPY sample.tmpl /tmp
25 changes: 25 additions & 0 deletions cmd/tinkerbell/cmd/hardware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package cmd

import (
"fmt"

"github.com/packethost/tinkerbell/cmd/tinkerbell/cmd/hardware"
"github.com/spf13/cobra"
)

var hardwareCmd = &cobra.Command{
Use: "hardware",
Short: "tinkerbell hardware client",
Example: "tinkerbell hardware [command]",
Args: func(c *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("%v requires arguments", c.UseLine())
}
return nil
},
}

func init() {
hardwareCmd.AddCommand(hardware.SubCommands...)
rootCmd.AddCommand(hardwareCmd)
}
37 changes: 37 additions & 0 deletions cmd/tinkerbell/cmd/hardware/all.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package hardware

import (
"context"
"fmt"
"io"
"log"

"github.com/packethost/tinkerbell/client"
"github.com/packethost/tinkerbell/protos/hardware"
"github.com/spf13/cobra"
)

// allCmd represents the all command
var allCmd = &cobra.Command{
Use: "all",
Short: "Get all known hardware for facility",
Run: func(cmd *cobra.Command, args []string) {
alls, err := client.HardwareClient.All(context.Background(), &hardware.Empty{})
if err != nil {
log.Fatal(err)
}

var hw *hardware.Hardware
err = nil
for hw, err = alls.Recv(); err == nil && hw != nil; hw, err = alls.Recv() {
fmt.Println(hw.JSON)
}
if err != nil && err != io.EOF {
log.Fatal(err)
}
},
}

func init() {
SubCommands = append(SubCommands, allCmd)
}
25 changes: 25 additions & 0 deletions cmd/tinkerbell/cmd/hardware/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package hardware

import (
"fmt"

"github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
"github.com/spf13/cobra"
)

// SubCommands holds the sub commands for template command
// Example: tinkerbell template [subcommand]
var SubCommands []*cobra.Command

func verifyUUIDs(args []string) error {
if len(args) < 1 {
return errors.New("requires at least one id")
}
for _, arg := range args {
if _, err := uuid.FromString(arg); err != nil {
return fmt.Errorf("invalid uuid: %s", arg)
}
}
return nil
}
36 changes: 36 additions & 0 deletions cmd/tinkerbell/cmd/hardware/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright © 2018 packet.net

package hardware

import (
"context"
"fmt"
"log"

"github.com/packethost/tinkerbell/client"
"github.com/packethost/tinkerbell/protos/hardware"
"github.com/spf13/cobra"
)

// idCmd represents the id command
var idCmd = &cobra.Command{
Use: "id",
Short: "Get hardware by id",
Example: "tinkerbell hardware id 224ee6ab-ad62-4070-a900-ed816444cec0 cb76ae54-93e9-401c-a5b2-d455bb3800b1",
Args: func(_ *cobra.Command, args []string) error {
return verifyUUIDs(args)
},
Run: func(cmd *cobra.Command, args []string) {
for _, id := range args {
hw, err := client.HardwareClient.ByID(context.Background(), &hardware.GetRequest{ID: id})
if err != nil {
log.Fatal(err)
}
fmt.Println(hw.JSON)
}
},
}

func init() {
SubCommands = append(SubCommands, idCmd)
}
32 changes: 32 additions & 0 deletions cmd/tinkerbell/cmd/hardware/ingest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright © 2018 packet.net

package hardware

import (
"context"
"fmt"
"log"

"github.com/packethost/tinkerbell/client"
"github.com/packethost/tinkerbell/protos/hardware"
"github.com/spf13/cobra"
)

// ingestCmd represents the ingest command
var ingestCmd = &cobra.Command{
Use: "ingest",
Short: "Trigger tinkerbell to ingest",
Long: "This command only signals tinkerbell to ingest if it has not already done so.",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("ingest called")
_, err := client.HardwareClient.Ingest(context.Background(), &hardware.Empty{})
if err != nil {
log.Fatal(err)
}
},
}

func init() {
SubCommands = append(SubCommands, ingestCmd)

}
42 changes: 42 additions & 0 deletions cmd/tinkerbell/cmd/hardware/ip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright © 2018 packet.net

package hardware

import (
"context"
"fmt"
"log"
"net"

"github.com/packethost/tinkerbell/client"
"github.com/packethost/tinkerbell/protos/hardware"
"github.com/spf13/cobra"
)

// ipCmd represents the ip command
var ipCmd = &cobra.Command{
Use: "ip",
Short: "Get hardware by any associated ip",
Example: "tinkerbell hardware ip 10.0.0.2 10.0.0.3",
Args: func(_ *cobra.Command, args []string) error {
for _, arg := range args {
if net.ParseIP(arg) == nil {
return fmt.Errorf("invalid ip: %s", arg)
}
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
for _, ip := range args {
hw, err := client.HardwareClient.ByIP(context.Background(), &hardware.GetRequest{IP: ip})
if err != nil {
log.Fatal(err)
}
fmt.Println(hw.JSON)
}
},
}

func init() {
SubCommands = append(SubCommands, ipCmd)
}
Loading