diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..dd01830
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,15 @@
+name: build
+
+on: push
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+
+ - name: Install Opctl
+ run: curl -L https://github.com/opctl/opctl/releases/download/0.1.45/opctl0.1.45.linux.tgz | sudo tar -xzv -C /usr/local/bin
+
+ - name: Build
+ run: opctl run build
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6ad0969
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+# Jetbrains
+.idea/
+
+# opctl
+*args*.yml
\ No newline at end of file
diff --git a/.opspec/build/op.yml b/.opspec/build/op.yml
new file mode 100644
index 0000000..da98b54
--- /dev/null
+++ b/.opspec/build/op.yml
@@ -0,0 +1,10 @@
+name: build
+description: builds the op
+run:
+ serial:
+ - op:
+ ref: github.com/opspec-pkgs/_.op.bootstrap#4.1.1
+ inputs:
+ srcDir: $(../..)
+ - op:
+ ref: test
\ No newline at end of file
diff --git a/.opspec/e2eTest/op.yml b/.opspec/e2eTest/op.yml
new file mode 100644
index 0000000..d038fbc
--- /dev/null
+++ b/.opspec/e2eTest/op.yml
@@ -0,0 +1,35 @@
+name: e2eTest
+run:
+ serial:
+ - op:
+ ref: $(../../)
+ inputs:
+ headers:
+ Content-Type:
+ - text/html
+ url: https://google.com
+ method: GET
+ outputs:
+ body:
+ headers:
+ protocol:
+ statusCode:
+ - container:
+ image:
+ ref: alpine
+ cmd:
+ - sh
+ - -ce
+ - |
+ echo $(protocol) $(statusCode)
+ echo 'Content-Type'
+ echo $(headers[Content-Type][0])
+ echo 'headers'
+ cat /headers
+ echo ""
+ echo "body"
+ cat /body
+ sleep 1
+ files:
+ /body: $(body)
+ /headers: $(headers)
\ No newline at end of file
diff --git a/.opspec/provision-image/op.yml b/.opspec/provision-image/op.yml
new file mode 100644
index 0000000..d4a3e87
--- /dev/null
+++ b/.opspec/provision-image/op.yml
@@ -0,0 +1,44 @@
+name: provision-image
+description: |
+ Provisions image for the op to [opspec-pkgs image registry](https://github.com/orgs/opspec-pkgs/packages) w/ name `$(opDotYml.name):${opDotYml.version}`.
+
+ > Before first use, a Dockerfile MUST be added to `.opspec/provision-image`
+inputs:
+ githubUsername:
+ string:
+ constraints: { minLength: 1 }
+ description: user for logging in to github
+ githubAccessToken:
+ string:
+ constraints: { minLength: 1 }
+ description: accessToken for logging in to github
+ isSecret: true
+run:
+ serial:
+ - op:
+ ref: github.com/opspec-pkgs/yaml.parse#1.0.0
+ inputs:
+ yaml: $(../../op.yml)
+ outputs:
+ result: $(opDotYml)
+ - container:
+ image: { ref: 'alpine:3.6' }
+ cmd: [sh, -ce, 'echo -n ghcr.io/opspec-pkgs/${opName#*/*/}:$(opDotYml.version) > /imageName']
+ envVars:
+ opName: $(opDotYml.name)
+ files:
+ /imageName: $(imageName)
+ - op:
+ ref: github.com/opspec-pkgs/docker.image.build#2.1.0
+ inputs:
+ dockerfile: $(./Dockerfile)
+ imageName:
+ outputs: { imageTar }
+ - op:
+ ref: github.com/opspec-pkgs/docker.image.push#2.1.0
+ inputs:
+ imageTar:
+ imageName:
+ registry: ghcr.io
+ username: $(githubUsername)
+ password: $(githubAccessToken)
diff --git a/.opspec/release/op.yml b/.opspec/release/op.yml
new file mode 100644
index 0000000..b03531c
--- /dev/null
+++ b/.opspec/release/op.yml
@@ -0,0 +1,43 @@
+name: release
+description: releases the op
+inputs:
+ githubUsername:
+ string:
+ constraints: { minLength: 1 }
+ description: user for logging in to github
+ githubAccessToken:
+ string:
+ constraints: { minLength: 1 }
+ description: accessToken for logging in to github
+ isSecret: true
+run:
+ serial:
+ - op:
+ ref: build
+ - op:
+ ref: github.com/opspec-pkgs/yaml.parse#1.0.0
+ inputs:
+ yaml: $(../../op.yml)
+ outputs:
+ result: $(opDotYml)
+ - op:
+ ref: github.com/opspec-pkgs/git.repo.resolve-commit#1.0.2
+ inputs: { dotGitDir: $(../../.git) }
+ outputs: { commit: }
+ - container:
+ image: { ref: 'alpine:3.6' }
+ cmd: [sh, -ce, 'echo -n ${opName#*/*/} > /opRepo']
+ envVars:
+ opName: $(opDotYml.name)
+ files:
+ /opRepo: $(opRepo)
+ - op:
+ ref: github.com/opspec-pkgs/github.release.create#1.1.0
+ inputs:
+ owner: opspec-pkgs
+ repo: $(opRepo)
+ loginUsername: $(githubUsername)
+ loginPassword: $(githubAccessToken)
+ tag: $(opDotYml.version)
+ commitish: $(commit)
+ name: $(opDotYml.version)
\ No newline at end of file
diff --git a/.opspec/test/op.yml b/.opspec/test/op.yml
new file mode 100644
index 0000000..5dc431e
--- /dev/null
+++ b/.opspec/test/op.yml
@@ -0,0 +1,8 @@
+name: test
+description: tests the op
+run:
+ op:
+ ref: github.com/opspec-pkgs/opctl.op.validate#1.0.0
+ inputs:
+ opctlVersion: 0.1.53
+ op: $(../..)
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..8e92fd9
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,6 @@
+# Change Log
+
+All notable changes will be documented in this file in accordance with
+[](http://keepachangelog.com/en/1.0.0/)
+
+## \[Unreleased]
\ No newline at end of file
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 0000000..60761a7
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1 @@
+* @opspec-pkgs/maintainers
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..72ddece
--- /dev/null
+++ b/README.md
@@ -0,0 +1,58 @@
+[](https://github.com/opspec-pkgs/http.send/actions?query=workflow%3Abuild+branch%3Amain)
+
+
+
+# Problem statement
+
+Sends an http request
+
+# Example usage
+
+## Visualize
+
+```shell
+opctl ui github.com/opspec-pkgs/http.send#1.0.0
+```
+
+## Run
+
+```
+opctl run github.com/opspec-pkgs/http.send#1.0.0
+```
+
+## Compose
+
+```yaml
+op:
+ ref: github.com/opspec-pkgs/http.send#1.0.0
+ inputs:
+ method: # 👈 required; provide a value
+ url: # 👈 required; provide a value
+ ## uncomment to override defaults
+ # body: "[object Object]"
+ # headers: [object Object]
+ outputs:
+ body:
+ headers:
+ protocol:
+ statusCode:
+```
+
+# Support
+
+join us on
+[](https://join.slack.com/t/opctl/shared_invite/zt-51zodvjn-Ul_UXfkhqYLWZPQTvNPp5w)
+or
+[open an issue](https://github.com/opspec-pkgs/http.send/issues)
+
+# Releases
+
+releases are versioned according to
+[](http://semver.org/spec/v2.0.0.html)
+and [tagged](https://git-scm.com/book/en/v2/Git-Basics-Tagging); see
+[CHANGELOG.md](CHANGELOG.md) for release notes
+
+# Contributing
+
+see
+[project/CONTRIBUTING.md](https://github.com/opspec-pkgs/project/blob/main/CONTRIBUTING.md)
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..71f42ed
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/opspec-pkgs/http.send
+
+go 1.23.2
diff --git a/go.sum b/go.sum
new file mode 100755
index 0000000..e69de29
diff --git a/icon.svg b/icon.svg
new file mode 100644
index 0000000..7851988
--- /dev/null
+++ b/icon.svg
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/internal/inout/doc.go b/internal/inout/doc.go
new file mode 100644
index 0000000..9746895
--- /dev/null
+++ b/internal/inout/doc.go
@@ -0,0 +1,2 @@
+// package inout is used to process op input to an http request and http response to op output
+package inout
\ No newline at end of file
diff --git a/internal/inout/getHeaders.go b/internal/inout/getHeaders.go
new file mode 100644
index 0000000..4183824
--- /dev/null
+++ b/internal/inout/getHeaders.go
@@ -0,0 +1,54 @@
+package inout
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+ "strconv"
+)
+
+func getHeaders() (
+ map[string][]string,
+ error,
+) {
+ headersJSONBytes, err := os.ReadFile(HeadersFilePath)
+ if err != nil {
+ return nil, err
+ }
+
+ rawHeaders := map[string][]interface{}{}
+ if err := json.Unmarshal(headersJSONBytes, &rawHeaders); err != nil {
+ return nil, err
+ }
+
+ headersMap := http.Header{}
+ for k, vSlice := range rawHeaders {
+ for _, rawV := range vSlice {
+ v, err := convertToString(rawV)
+ if err != nil {
+ return nil, err
+ }
+
+ headersMap.Add(k, v)
+ }
+ }
+
+ return headersMap, nil
+}
+
+func convertToString(
+ in interface{},
+) (string, error) {
+ if s, ok := in.(string); ok {
+ return s, nil
+ } else if b, ok := in.(bool); ok {
+ return strconv.FormatBool(b), nil
+ } else if f, ok := in.(float64); ok {
+ return strconv.FormatFloat(f, 'f', -1, 64), nil
+ } else if i, ok := in.(int64); ok {
+ return strconv.FormatInt(i, 10), nil
+ }
+
+ return "", fmt.Errorf("%+v not a valid header value", in)
+}
diff --git a/internal/inout/getRequest.go b/internal/inout/getRequest.go
new file mode 100644
index 0000000..ee14269
--- /dev/null
+++ b/internal/inout/getRequest.go
@@ -0,0 +1,46 @@
+package inout
+
+import (
+ "net/http"
+ "os"
+)
+
+func GetRequest() (
+ *http.Request,
+ error,
+) {
+ methodBytes, err := os.ReadFile(MethodFilePath)
+ if err != nil {
+ return nil, err
+ }
+
+ urlBytes, err := os.ReadFile(URLFilePath)
+ if err != nil {
+ return nil, err
+ }
+
+ reqBody, err := os.Open(BodyFilePath)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest(
+ string(methodBytes),
+ string(urlBytes),
+ reqBody,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ if req.ContentLength == 0 {
+ req.Body = nil
+ }
+
+ req.Header, err = getHeaders()
+ if err != nil {
+ return nil, err
+ }
+
+ return req, nil
+}
diff --git a/internal/inout/paths.go b/internal/inout/paths.go
new file mode 100644
index 0000000..dd0346d
--- /dev/null
+++ b/internal/inout/paths.go
@@ -0,0 +1,10 @@
+package inout
+
+const (
+ BodyFilePath = "/body.txt"
+ HeadersFilePath = "/headers.json"
+ MethodFilePath = "/method.txt"
+ ProtocolFilePath = "/protocol.txt"
+ StatusCodeFilePath = "/statusCode.txt"
+ URLFilePath = "/url.txt"
+)
\ No newline at end of file
diff --git a/internal/inout/setBody.go b/internal/inout/setBody.go
new file mode 100644
index 0000000..c48c485
--- /dev/null
+++ b/internal/inout/setBody.go
@@ -0,0 +1,20 @@
+package inout
+
+import (
+ "io"
+ "os"
+)
+
+func setBody(
+ body io.ReadCloser,
+) error {
+ bodyFile, err := os.Create(BodyFilePath)
+ if err != nil {
+ return err
+ }
+ defer bodyFile.Close()
+
+ _, err = io.Copy(bodyFile, body)
+ defer body.Close()
+ return err
+}
diff --git a/internal/inout/setHeaders.go b/internal/inout/setHeaders.go
new file mode 100644
index 0000000..0d84635
--- /dev/null
+++ b/internal/inout/setHeaders.go
@@ -0,0 +1,22 @@
+package inout
+
+import (
+ "encoding/json"
+ "net/http"
+ "os"
+)
+
+func setHeaders(
+ headers http.Header,
+) error {
+ headersJSONBytes, err := json.Marshal(headers)
+ if err != nil {
+ return err
+ }
+
+ return os.WriteFile(
+ HeadersFilePath,
+ headersJSONBytes,
+ 0644,
+ )
+}
diff --git a/internal/inout/setResponse.go b/internal/inout/setResponse.go
new file mode 100644
index 0000000..66d080b
--- /dev/null
+++ b/internal/inout/setResponse.go
@@ -0,0 +1,27 @@
+package inout
+
+import (
+ "net/http"
+ "os"
+ "strconv"
+)
+
+func SetResponse(
+ resp *http.Response,
+) error {
+
+ if err := setBody(resp.Body); err != nil {
+ return err
+ }
+ if err := setHeaders(resp.Header); err != nil {
+ return err
+ }
+ if err := os.WriteFile(ProtocolFilePath, []byte(resp.Proto), 0644); err != nil {
+ return err
+ }
+ if err := os.WriteFile(StatusCodeFilePath, []byte(strconv.Itoa(resp.StatusCode)), 0644); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..f9c69cb
--- /dev/null
+++ b/main.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "net/http"
+
+ "github.com/opspec-pkgs/http.send/internal/inout"
+)
+
+func main() {
+
+ req, err := inout.GetRequest()
+ if err != nil {
+ panic(err)
+ }
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ panic(err)
+ }
+
+ if err := inout.SetResponse(resp); err != nil {
+ panic(err)
+ }
+
+}
\ No newline at end of file
diff --git a/op.yml b/op.yml
new file mode 100644
index 0000000..4b5c468
--- /dev/null
+++ b/op.yml
@@ -0,0 +1,58 @@
+description: Sends an http request
+name: github.com/opspec-pkgs/http.send
+inputs:
+ body:
+ description: body/content of the request
+ string:
+ default: {}
+ headers:
+ description: headers of the request
+ object:
+ constraints:
+ patternProperties:
+ ".+":
+ type: array
+ default: {}
+ method:
+ description: method of the request e.g. GET, HEAD, POST, PUT, DELETE, etc...
+ string:
+ constraints:
+ minLength: 1
+ url:
+ description: url of the request
+ string:
+ constraints:
+ format: uri
+outputs:
+ body:
+ description: body/content of the response
+ string:
+ default: {}
+ headers:
+ description: headers of the response
+ object: {}
+ protocol:
+ description: protocol of the response e.g. HTTP/1.1
+ string: {}
+ statusCode:
+ description: status code of the response
+ number: {}
+run:
+ container:
+ cmd:
+ - go
+ - run
+ - /src/main.go
+ dirs:
+ /src: $(./)
+ image:
+ ref: golang:1.23
+ files:
+ /body.txt: $(body)
+ /headers.json: $(headers)
+ /method.txt: $(method)
+ /protocol.txt: $(protocol)
+ /statusCode.txt: $(statusCode)
+ /url.txt: $(url)
+ workDir: /src
+version: 1.0.0