diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..4c1b11c --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,58 @@ +# Code generated by github.com/heetch/cue-schema/github/workflow/generate. DO NOT EDIT. +jobs: + test: + runs-on: ${{ matrix.platform }} + services: + kafka: + env: + KAFKA_ADVERTISED_LISTENERS: interbroker://kafka:29092,fromclient://localhost:9092 + KAFKA_BROKER_ID: "1" + KAFKA_INTER_BROKER_LISTENER_NAME: interbroker + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: interbroker:PLAINTEXT,fromclient:PLAINTEXT + KAFKA_LOG4J_ROOT_LOGLEVEL: DEBUG + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1" + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: "10000" + image: confluentinc/cp-kafka:latest + ports: + - 9092:9092 + zookeeper: + env: + ZOOKEEPER_CLIENT_PORT: "2181" + ZOOKEEPER_LOG4J_ROOT_LOGLEVEL: DEBUG + ZOOKEEPER_TICK_TIME: "2000" + image: confluentinc/cp-zookeeper:latest + ports: + - 2181:2181 + steps: + - name: Install Go + uses: actions/setup-go@v1 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v1 + - name: Wait for Kafka + run: "waitfor() {\n\twhile ! nc -v -z $1 $2\n\tdo sleep 1\n\tdone\n}\nwaitfor + localhost 9092\nwaitfor localhost 2181\n" + shell: bash + timeout-minutes: 1 + - name: Test + run: "echo ip address of kafka:\ndocker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' + \"${{ job.services.kafka.id }}\"\necho ip address of zookeeper:\ndocker inspect + -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \"${{ job.services.zookeeper.id + }}\"\n\n\nwhile nc -v -z localhost 9092; do\n\techo ok so far\n\tsleep 1\ndone\necho + ------------- kafka logs\ndocker logs \"${{ job.services.kafka.id }}\"\necho + --------------- end kafka logs\necho\necho ------------- zookeeper logs\ndocker + logs \"${{ job.services.zookeeper.id }}\"\necho ---------------- end zookeeper + logs\ndocker ps\nexit 1\nexport KAFKA_ADDRS=localhost:9092\n#go test ./...\ngo + run dial.go $KAFKA_ADDRS\ngo test -mod=vendor ./...\n" + strategy: + matrix: + go-version: + - 1.13.x + platform: + - ubuntu-latest +name: Test +"on": +- push +- pull_request diff --git a/client.go b/client.go index 64ad3bb..05c9718 100644 --- a/client.go +++ b/client.go @@ -1,188 +1,23 @@ -// Package kafkatest provides a package intended for running tests -// that require a Kafka backend. -package kafkatest +package main import ( - "crypto/rand" "fmt" + "net" "os" - "strconv" - "strings" - "time" - - "github.com/Shopify/sarama" - "gopkg.in/retry.v1" ) -var ErrDisabled = fmt.Errorf("kafka tests are disabled") - -// New connects to a Kafka instance and returns a Kafka -// instance that uses it. -// -// The following environment variables can be used to -// configure the connection parameters: -// -// - $KAFKA_DISABLE -// A boolean as parsed by strconv.ParseBool. If this is true, -// New will return ErrDisabled. -// - $KAFKA_ADDRS -// A comma-separate list of Kafka broker addresses in host:port -// form. If this is empty, localhost:9092 will be used. -// The list of address can be discovered by calling Client.Addrs. -// - $KAFKA_USERNAME, $KAFKA_PASWORD -// The username and password to use for SASL authentication. -// When $KAFKA_USERNAME is non-empty, SASL will be -// enabled. -// - $KAFKA_USE_TLS -// A boolean as parsed by strconv.ParseBool. If this -// is true, a secure TLS connection will be used. -// -// - $KAFKA_TIMEOUT -// The maximum duration to wait when trying to connect -// to Kakfa. Defaults to "30s". -// -// The returned Kafka instance must be closed after use. -func New() (*Kafka, error) { - disabled, err := boolVar("KAFKA_DISABLE") +func main() { + err := DialIt(os.Args[1]) + fmt.Printf("dial %s: %v\n", os.Args[1], err) if err != nil { - return nil, fmt.Errorf("bad value for $KAFKA_DISABLE: %v", err) - } - if disabled { - return nil, ErrDisabled - } - addrsStr := os.Getenv("KAFKA_ADDRS") - if addrsStr == "" { - addrsStr = "localhost:9092" - } - addrs := strings.Split(addrsStr, ",") - useTLS, err := boolVar("KAFKA_USE_TLS") - if err != nil { - return nil, fmt.Errorf("bad value for KAFKA_USE_TLS: %v", err) - } - client := &Kafka{ - addrs: addrs, - useTLS: useTLS, - saslUser: os.Getenv("KAFKA_USERNAME"), - saslPassword: os.Getenv("KAFKA_PASSWORD"), - } - // The cluster might not be available immediately, so try - // for a while before giving up. - retryLimit := 30 * time.Second - if limit := os.Getenv("KAFKA_TIMEOUT"); limit != "" { - retryLimit, err = time.ParseDuration(limit) - if err != nil { - return nil, fmt.Errorf("bad value for KAFKA_TIMEOUT: %v", err) - } - } - retryStrategy := retry.LimitTime(retryLimit, retry.Exponential{ - Initial: time.Millisecond, - MaxDelay: time.Second, - }) - for a := retry.Start(retryStrategy, nil); a.Next(); { - admin, err := sarama.NewClusterAdmin(addrs, client.Config()) - if err == nil { - client.admin = admin - break - } - if !a.Next() { - return nil, fmt.Errorf("cannot connect to Kafka cluster at %q after %v: %v", addrs, retryLimit, err) - } - } - return client, nil -} - -// Kafka represents a connection to a Kafka cluster. -type Kafka struct { - addrs []string - useTLS bool - saslUser string - saslPassword string - admin sarama.ClusterAdmin - topics []string -} - -// Config returns a sarama configuration that will -// use connection parameters defined in the environment -// variables described in New. -func (k *Kafka) Config() *sarama.Config { - cfg := sarama.NewConfig() - k.InitConfig(cfg) - return cfg -} - -// InitConfig is similar to Config, except that instead of -// returning a new configuration, it configures an existing -// one. -func (k *Kafka) InitConfig(cfg *sarama.Config) { - if cfg.Version == sarama.MinVersion { - // R - cfg.Version = sarama.V1_0_0_0 - } - cfg.Net.TLS.Enable = k.useTLS - if k.saslUser != "" { - cfg.Net.SASL.Enable = true - cfg.Net.SASL.User = k.saslUser - cfg.Net.SASL.Password = k.saslPassword + os.Exit(1) } } -// Addrs returns the configured Kakfa broker addresses. -func (k *Kafka) Addrs() []string { - return k.addrs -} - -// NewTopic creates a new Kafka topic with a random name and -// single partition. It returns the topic's name. The topic will be deleted -// when c.Close is called. -// -// NewTopic panics if the topic cannot be created. -func (k *Kafka) NewTopic() string { - if k.admin == nil { - panic("cannot create topic with closed kafkatest.Kafka instance") - } - topic := randomName("kafkatest-") - if err := k.admin.CreateTopic(topic, &sarama.TopicDetail{ - NumPartitions: 1, - ReplicationFactor: 1, - }, false); err != nil { - panic(fmt.Errorf("cannot create topic %q: %v", topic, err)) - } - k.topics = append(k.topics, topic) - return topic -} - -// Close closes the client connection and removes any topics -// created by Topic. This method may be called more than once. -func (k *Kafka) Close() error { - if k.admin == nil { - return nil - } - for ; len(k.topics) != 0; k.topics = k.topics[1:] { - if err := k.admin.DeleteTopic(k.topics[0]); err != nil { - return fmt.Errorf("cannot delete topic %q: %v", k.topics[0], err) - } - } - k.admin.Close() - k.admin = nil - return nil -} - -func boolVar(envVar string) (bool, error) { - s := os.Getenv(envVar) - if s == "" { - return false, nil - } - b, err := strconv.ParseBool(s) +func DialIt(s string) error { + _, err := net.Dial("tcp", s) if err != nil { - return false, fmt.Errorf("invalid boolean value %q (possible values are: 1, t, T, TRUE, true, True, 0, f, F, FALSE)", s) + return fmt.Errorf("dialit: %v", err) } - return b, nil -} - -func randomName(prefix string) string { - buf := make([]byte, 8) - if _, err := rand.Read(buf); err != nil { - panic(err) - } - return fmt.Sprintf("%s%x", prefix, buf) + return nil } diff --git a/client_test.go b/client_test.go index 7adafe7..c2d2762 100644 --- a/client_test.go +++ b/client_test.go @@ -1,86 +1,13 @@ -package kafkatest_test +package main import ( + "os" "testing" - "time" - - "github.com/Shopify/sarama" - qt "github.com/frankban/quicktest" - - "github.com/heetch/kafkatest" ) -func TestNew(t *testing.T) { - c := qt.New(t) - k, err := kafkatest.New() - c.Assert(err, qt.Equals, nil) - - // Produce a message to a new topic. - cfg := k.Config() - cfg.Producer.Return.Successes = true - cfg.Producer.Return.Errors = true - - producer, err := sarama.NewSyncProducer(k.Addrs(), cfg) - c.Assert(err, qt.Equals, nil) - defer producer.Close() - topic := k.NewTopic() - - // Check that the topic has actually been created. - admin, err := sarama.NewClusterAdmin(k.Addrs(), cfg) - c.Assert(err, qt.Equals, nil) - defer admin.Close() - topics, err := admin.ListTopics() - c.Assert(err, qt.Equals, nil) - _, ok := topics[topic] - c.Assert(ok, qt.Equals, true) - - // Produce a message to the topic. - _, offset, err := producer.SendMessage(&sarama.ProducerMessage{ - Topic: topic, - Key: sarama.StringEncoder("key"), - Value: sarama.StringEncoder("value"), - }) - c.Assert(err, qt.Equals, nil) - c.Assert(offset, qt.Equals, int64(0)) - - // Check that we can consume the message we just produced. - consumer, err := sarama.NewConsumer(k.Addrs(), cfg) - c.Assert(err, qt.Equals, nil) - defer consumer.Close() - pconsumer, err := consumer.ConsumePartition(topic, 0, sarama.OffsetOldest) - c.Assert(err, qt.Equals, nil) - defer pconsumer.Close() - select { - case m := <-pconsumer.Messages(): - c.Check(string(m.Key), qt.Equals, "key") - c.Check(string(m.Value), qt.Equals, "value") - case <-time.After(10 * time.Second): - c.Fatal("timed out waiting for message") +func TestFoo(t *testing.T) { + err := DialIt(os.Getenv("KAFKA_ADDRS")) + if err != nil { + t.Errorf("cannot dial %q: %v", os.Getenv("KAFKA_ADDRS"), err) } - - // Close the Kafka instance and check that the - // new topic has been removed. - err = k.Close() - c.Assert(err, qt.Equals, nil) - topics, err = admin.ListTopics() - c.Assert(err, qt.Equals, nil) - _, ok = topics[topic] - c.Assert(ok, qt.Equals, false) - - // Check we can call Close again. - err = k.Close() - c.Assert(err, qt.Equals, nil) -} - -func TestDisabled(t *testing.T) { - c := qt.New(t) - c.Setenv("KAFKA_DISABLE", "1") - k, err := kafkatest.New() - c.Assert(err, qt.Equals, kafkatest.ErrDisabled) - c.Assert(k, qt.IsNil) - - c.Setenv("KAFKA_DISABLE", "bad") - k, err = kafkatest.New() - c.Assert(err, qt.ErrorMatches, `bad value for \$KAFKA_DISABLE: invalid boolean value "bad" \(possible values are: 1, t, T, TRUE, true, True, 0, f, F, FALSE\)`) - c.Assert(k, qt.IsNil) } diff --git a/cue.mod/module.cue b/cue.mod/module.cue new file mode 100644 index 0000000..f8af9ce --- /dev/null +++ b/cue.mod/module.cue @@ -0,0 +1 @@ +module: "" diff --git a/cue.mod/pkg/github.com/heetch/cue-schema/github/cue.mod/module.cue b/cue.mod/pkg/github.com/heetch/cue-schema/github/cue.mod/module.cue new file mode 100644 index 0000000..d626ddc --- /dev/null +++ b/cue.mod/pkg/github.com/heetch/cue-schema/github/cue.mod/module.cue @@ -0,0 +1 @@ +module: "github.com/heetch/cue-schema/github" diff --git a/cue.mod/pkg/github.com/heetch/cue-schema/github/generate/generate-workflow.cue b/cue.mod/pkg/github.com/heetch/cue-schema/github/generate/generate-workflow.cue new file mode 100644 index 0000000..a1f9cc4 --- /dev/null +++ b/cue.mod/pkg/github.com/heetch/cue-schema/github/generate/generate-workflow.cue @@ -0,0 +1,19 @@ +package generate + +import ( + "tool/file" + "encoding/yaml" + "github.com/heetch/cue-schema/github/workflow" +) + +Workflow :: workflow + +command: generateworkflow: { + task: write: file.Create & { + filename: ".github/workflows/test.yaml" + contents: """ + # Code generated by github.com/heetch/cue-schema/github/workflow/generate. DO NOT EDIT. + \(yaml.Marshal(Workflow)) + """ + } +} diff --git a/cue.mod/pkg/github.com/heetch/cue-schema/github/workflow/event.cue b/cue.mod/pkg/github.com/heetch/cue-schema/github/workflow/event.cue new file mode 100644 index 0000000..252fa85 --- /dev/null +++ b/cue.mod/pkg/github.com/heetch/cue-schema/github/workflow/event.cue @@ -0,0 +1,217 @@ +package workflow + +Event :: + "check_run" | + "check_suite" | + "commit_comment" | + "create" | + "delete" | + "deployment" | + "deployment_status" | + "fork" | + "gollum" | + "issue_comment" | + "issues" | + "label" | + "member" | + "milestone" | + "page_build" | + "project" | + "project_card" | + "project_column" | + "public" | + "pull_request" | + "pull_request_review" | + "pull_request_review_comment" | + "push" | + "release" | + "repository_dispatch" | + "schedule" | + "status" | + "watch" + +EventConfig :: { + check_run?: null | { + type = + "created" | + "rerequested" | + "completed" | + "requested_action" + types?: [ ... type] + } + check_suite?: null | { + type = + "assigned" | + "unassigned" | + "labeled" | + "unlabeled" | + "opened" | + "edited" | + "closed" | + "reopened" | + "synchronize" | + "ready_for_review" | + "locked" | + "unlocked " | + "review_requested " | + "review_request_removed" + types?: [ ... type] + } + commit_comment?: null | {} + create?: null | {} + delete?: null | {} + deployment?: null | {} + deployment_status?: null | {} + fork?: null | {} + gollum?: null | {} + issue_comment?: null | { + type = + "created" | + "edited" | + "deleted" + types?: [ ... type] + } + issues?: null | { + type = + "opened" | + "edited" | + "deleted" | + "transferred" | + "pinned" | + "unpinned" | + "closed" | + "reopened" | + "assigned" | + "unassigned" | + "labeled" | + "unlabeled" | + "locked" | + "unlocked" | + "milestoned" | + "demilestoned" + types?: [ ... type] + } + label?: null | { + type = + "created" | + "edited" | + "deleted" + types?: [ ... type] + } + member?: null | { + type = + "added" | + "edited" | + "deleted" + types?: [ ... type] + } + milestone?: null | { + type = + "created" | + "closed" | + "opened" | + "edited" | + "deleted" + types?: [ ... type] + } + page_build?: null | {} + project?: null | { + type = + "created" | + "closed" | + "opened" | + "edited" | + "deleted" + types?: [ ... type] + } + project_card?: null | { + type = + "created" | + "moved" | + "converted" | + "edited" | + "deleted" + types?: [ ... type] + } + project_column?: null | { + type = + "created" | + "updated" | + "moved" | + "deleted" + types?: [ ... type] + } + public?: null | {} + pull_request?: null | { + PushPullEvent + type = + "assigned" | + "unassigned" | + "labeled" | + "unlabeled" | + "opened" | + "edited" | + "closed" | + "reopened" | + "synchronize" | + "ready_for_review" | + "locked" | + "unlocked " | + "review_requested " | + "review_request_removed" + types?: [ ... type] + } + pull_request_review?: null | { + type = + "submitted" | + "edited" | + "dismissed" + types?: [ ... type] + } + pull_request_review_comment?: null | { + type = + "created" | + "edited" | + "deleted" + types?: [ ... type] + } + push?: null | { + PushPullEvent + } + release?: null | { + type = + "published " | + "unpublished " | + "created " | + "edited " | + "deleted " | + "prereleased" + types? : [ ... type] + } + repository_dispatch?: null | {} + // schedule configures a workflow to run at specific UTC times using POSIX + // cron syntax. Scheduled workflows run on the latest commit on the + // default or base branch. + schedule?: null | [{ + // cron specifies the time schedule in cron syntax. + // See https://help.github.com/en/articles/events-that-trigger-workflows#scheduled-events + // TODO regexp for cron syntax + cron: string + }] + status?: null | {} + watch?: null | { + types?: [ "started"] + } +} + +PushPullEvent :: { + branches?: [... Glob] + tags?: [... Glob] + "branches-ignore"?: [... Glob] + "tags-ignore"?: [... Glob] + paths?: [... Glob] +} + +// Glob represents a wildcard pattern. +// See https://help.github.com/en/articles/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet +Glob :: string diff --git a/cue.mod/pkg/github.com/heetch/cue-schema/github/workflow/example/go/go.cue b/cue.mod/pkg/github.com/heetch/cue-schema/github/workflow/example/go/go.cue new file mode 100644 index 0000000..6beeac1 --- /dev/null +++ b/cue.mod/pkg/github.com/heetch/cue-schema/github/workflow/example/go/go.cue @@ -0,0 +1,12 @@ +package go + +import ( + goworkflow "github.com/heetch/cue-schema/github/workflow/go:workflow" +) + +Workflow :: goworkflow +Workflow :: { + name: "My CI caboodle" + Versions :: ["v1.12", "v1.13"] + Services :: ["postgres", "kafka"] +} diff --git a/cue.mod/pkg/github.com/heetch/cue-schema/github/workflow/go/go.cue b/cue.mod/pkg/github.com/heetch/cue-schema/github/workflow/go/go.cue new file mode 100644 index 0000000..bdf6132 --- /dev/null +++ b/cue.mod/pkg/github.com/heetch/cue-schema/github/workflow/go/go.cue @@ -0,0 +1,143 @@ + +// Go-centric defaults for github actions. +// +// This defines a single test job with appropriate defaults. +// Override the Platforms, Versions, RunTest and Services definitions +// for easy modification of some parameters. +package workflow + +import "list" + +on: _ | *["push", "pull_request"] +name: _ | *"Test" +jobs: test: { + strategy: matrix: { + "go-version": _ | *[ "\(v).x" for v in Versions ] + platform: _ | *[ "\(p)-latest" for p in Platforms ] + } + "runs-on": "${{ matrix.platform }}" + steps: list.FlattenN([{ + name: "Install Go" + uses: "actions/setup-go@v1" + with: "go-version": "${{ matrix.go-version }}" + }, +// { +// name: "Module cache" +// uses: "actions/cache@v1" +// with: { +// path: "~/go/pkg/mod" +// key: "${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}" +// "restore-keys": "${{ runner.os }}-go-" +// } +// }, + { + name: "Checkout code" + uses: "actions/checkout@v1" + }, + // Include setup steps for any services that require them, + [ServiceConfig[name].SetupStep for name, enabled in Services if enabled && ServiceConfig[name].SetupStep != null], + _ | *{ + name: "Test" + run: RunTest + }], 1) +} + +// Include all named services. +for name, enabled in Services if enabled { + jobs: test: services: "\(name)": ServiceConfig[name].Service +} + +// Platforms configures what platforms to run the tests on. +Platforms :: *["ubuntu"] | [ ... "ubuntu" | "macos" | "windows"] + +// Versions configures what Go versions to run the tests on. +// TODO regexp.Match("^1.[0-9]+$") +Versions :: *["1.13"] | [ ... string] + +// RunTest configures the command used to run the tests. +RunTest :: *"go test ./..." | string + +// Service configures which services to make available. +// The configuration the service with name N is taken from +// ServiceConfig[N] +Services :: [_]: bool + +// ServiceConfig holds the default configuration for services that +// can be started by naming them in Services. +ServiceConfig :: [_]: { + // Service holds the contents of `jobs: test: services: "\(serviceName)"` + "Service": Service + + // SetupStep optionally holds a step to run to set up the service + // before the main workflow action is run (for example to wait + // for the service to become ready). + SetupStep: JobStep | *null +} + +// Kafka requires zookeeper too. +if Services["kafka"] != _|_ { + Services :: zookeeper: true +} + +KafkaPort :: 9092 + +ServiceConfig :: kafka: { + Service: { + image: "confluentinc/cp-kafka:latest" + ports: ["\(KafkaPort):\(KafkaPort)"] + env: { + // See https://docs.confluent.io/current/kafka/multi-node.html + // and https://kafka.apache.org/documentation/#brokerconfigs + // for information on these settings. + KAFKA_BROKER_ID: "1" + KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" + KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: "10000" + KAFKA_ADVERTISED_LISTENERS: "interbroker://kafka:29092,fromclient://localhost:\(KafkaPort)" + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "interbroker:PLAINTEXT,fromclient:PLAINTEXT" + KAFKA_INTER_BROKER_LISTENER_NAME: "interbroker" + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1" + KAFKA_LOG4J_ROOT_LOGLEVEL: "DEBUG" + } + } + SetupStep: { + name: "Wait for Kafka" + "timeout-minutes": 1 + shell: "bash" + run: """ + waitfor() { + while ! nc -v -z $1 $2 + do sleep 1 + done + } + waitfor localhost \(KafkaPort) + waitfor localhost 2181 + + """ + } +} + +ServiceConfig :: zookeeper: { + Service: { + image: "confluentinc/cp-zookeeper:latest" + ports: ["2181:2181"] + env: { + // See https://docs.confluent.io/current/installation/docker/config-reference.html + ZOOKEEPER_CLIENT_PORT: "2181" + ZOOKEEPER_TICK_TIME: "2000" + ZOOKEEPER_LOG4J_ROOT_LOGLEVEL: "DEBUG" + } + } +} + +ServiceConfig :: postgres: _ |*{ + Service: { + image: "postgres:10.8" + ports: ["5432:5432"] + env: { + POSTGRES_DB: "postgres" + POSTGRES_PASSWORD: "postgres" + POSTGRES_USER: "postgres" + } + options: "--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5" + } +} diff --git a/cue.mod/pkg/github.com/heetch/cue-schema/github/workflow/workflow.cue b/cue.mod/pkg/github.com/heetch/cue-schema/github/workflow/workflow.cue new file mode 100644 index 0000000..3a68e07 --- /dev/null +++ b/cue.mod/pkg/github.com/heetch/cue-schema/github/workflow/workflow.cue @@ -0,0 +1,95 @@ + +// Package workflow describes the contents of a file in the +// .github/workflows directory of a repository. +// +// This follows the API described here: +// https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions +package workflow + +// TODO cross-verify against https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/github-workflow.json + +// name holds the name of your workflow. GitHub displays the +// names of your workflows on your repository's actions page. If +// you omit this field, GitHub sets the name to the workflow's +// filename. +name?: string + +// On describes the event or events that trigger the workflow. +on: Event | [... Event] | EventConfig + +env?: Env +jobs: { + // TODO must start with a letter or _ and contain only alphanumeric characters, -, or _. + [_]: Job +} + +// Job represents one of the jobs to do as part of the action. +Job :: { + name?: string + // TODO restrict JobID to names mentioned in the workflow jobs? + needs?: JobID | [...JobID] + "runs-on"?: string | [string, ...string] + env?: Env + if?: Condition + steps?: [... JobStep] + "timeout-minutes"?: number + container?: { + image?: string + env?: Env + ports?: [ ... int] + volumes?: [ ... string] + options?: _ // TODO more specific type here + } + strategy?: { + matrix: [_]: [ ...] + "fail-fast"?: bool + "max-parallel"?: int & >0 + } + services: { + [_]: Service + } +} + +Service :: { + image: string + env?: Env + ports?: [... string] + volumes?: [ ... string] + options?: _ // TODO more specific type here +} + +JobStep :: { + id?: string + if?: Condition + name?: string + uses?: string + run?: string + "working-directory"?: string + shell?: + "bash" | + "pwsh" | + "python" | + "sh" | + "cmd" | + "powershell" | + =~#"\{0\}"# + + // with holds a map of the input parameters defined by the action. + // Each input parameter is a key/value pair. Input parameters are + // set as environment variables. The variable is prefixed with INPUT_ + // and converted to upper case. + with?: [_]: string + env?: Env + "continue-on-error"?: bool + "timeout-minutes"?: number +} + +// JobID represents one the of jobs specified in Workflow. +JobID :: string + +// Condition represents a condition to evaluate. +// TODO link to syntax. +Condition :: string + +// Env represents a set of environment variables and their values. +Env :: [_]: string diff --git a/dial.go b/dial.go new file mode 100644 index 0000000..351c6e0 --- /dev/null +++ b/dial.go @@ -0,0 +1,17 @@ +//+build ignore + +package main + +import ( + "fmt" + "net" + "os" +) + +func main() { + _, err := net.Dial("tcp", os.Args[1]) + fmt.Printf("dial %s: %v\n", os.Args[1], err) + if err != nil { + os.Exit(1) + } +} diff --git a/github-action_tool.cue b/github-action_tool.cue new file mode 100644 index 0000000..9a55df1 --- /dev/null +++ b/github-action_tool.cue @@ -0,0 +1,55 @@ +package kafkatestCI + +import ( + "encoding/yaml" + "tool/file" + + "github.com/heetch/cue-schema/github/workflow/go:workflow" +) + +// To generate the github actions workflow file, run: +// +// cue generateworkflow + +Workflow :: workflow +Workflow :: Services :: { + kafka: true + zookeeper: true +} + +Workflow :: RunTest :: """ + echo ip address of kafka: + docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "${{ job.services.kafka.id }}" + echo ip address of zookeeper: + docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "${{ job.services.zookeeper.id }}" + + + while nc -v -z localhost \(Workflow.KafkaPort); do + echo ok so far + sleep 1 + done + echo ------------- kafka logs + docker logs "${{ job.services.kafka.id }}" + echo --------------- end kafka logs + echo + echo ------------- zookeeper logs + docker logs "${{ job.services.zookeeper.id }}" + echo ---------------- end zookeeper logs + docker ps + exit 1 + export KAFKA_ADDRS=localhost:\(Workflow.KafkaPort) + #go test ./... + go run dial.go $KAFKA_ADDRS + go test -mod=vendor ./... + + """ + +command: generateworkflow: { + task: write: file.Create & { + filename: ".github/workflows/test.yaml" + contents: """ + # Code generated by github.com/heetch/cue-schema/github/workflow/generate. DO NOT EDIT. + \(yaml.Marshal(Workflow)) + """ + } +} diff --git a/go.mod b/go.mod index 74ef139..ef49431 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,3 @@ module github.com/heetch/kafkatest go 1.14 - -require ( - github.com/Shopify/sarama v1.24.1 - github.com/frankban/quicktest v1.4.1 - github.com/klauspost/cpuid v1.2.1 // indirect - gopkg.in/retry.v1 v1.0.3 -) diff --git a/go.sum b/go.sum index 73a2318..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,69 +0,0 @@ -github.com/Shopify/sarama v1.24.1 h1:svn9vfN3R1Hz21WR2Gj0VW9ehaDGkiOS+VqlIcZOkMI= -github.com/Shopify/sarama v1.24.1/go.mod h1:fGP8eQ6PugKEI0iUETYYtnP6d1pH/bdDMTel1X5ajsU= -github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= -github.com/frankban/quicktest v1.4.1 h1:Wv2VwvNn73pAdFIVUQRXYDFp31lXKbqblIXo/Q5GPSg= -github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM= -github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= -github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pierrec/lz4 v2.2.6+incompatible h1:6aCX4/YZ9v8q69hTyiR7dNLnTA3fgtKHVVW5BCd5Znw= -github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a h1:3QH7VyOaaiUHNrA9Se4YQIRkDTCw1EJls9xTUCaCeRM= -github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a/go.mod h1:4r5QyqhjIWCcK8DO4KMclc5Iknq5qVBAlbYYzAbUScQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= -gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= -gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI= -gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= -gopkg.in/jcmturner/gokrb5.v7 v7.2.3 h1:hHMV/yKPwMnJhPuPx7pH2Uw/3Qyf+thJYlisUc44010= -gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= -gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= -gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= -gopkg.in/retry.v1 v1.0.3 h1:a9CArYczAVv6Qs6VGoLMio99GEs7kY9UzSF9+LD+iGs= -gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g=