diff --git a/README.md b/README.md index 4d6df4f..62ff964 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,6 @@ -# Arctiq Weather App - utilized in an OpenShift Pipeline +# Arctiq Weather App Simple Node.js Command Line Weather Application -* Built upon bmorielli25's [weather-app](https://github.com/bmorelli25/simple-nodejs-weather-app) - -This repo was used to build an app and promote across different OpenShift clusters in this [demo](https://www.arctiq.ca/our-blog/2018/10/6/multi-cloud-application-container-deployment-pipeline/) - -### Pre-requisites - -* [Skopeo Jenkins slave image for image promotion](https://github.com/redhat-cop/containers-quickstarts/tree/master/jenkins-slaves/jenkins-slave-image-mgmt) -* 3 OpenShift clusters: - * DEV/QA Cluster - * PROD A Cluster - * PROD B Cluster -* Replace cluster URLs in snippets. - -### How to run: - -`oc create -f jenkins/pipeline.yaml` - -### Run the pipeline and see the weather! - -![gif](https://github.com/arctiqteam/weather-app/blob/master/giphy.gif?raw=true 'website gif') +``` +$ cf push +``` diff --git a/config-management/namespaces/weather/deployment.yaml b/config-management/namespaces/weather/deployment.yaml new file mode 100644 index 0000000..b6675ab --- /dev/null +++ b/config-management/namespaces/weather/deployment.yaml @@ -0,0 +1,123 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "49" + labels: + app.kubernetes.io/component: app-scaler + app.kubernetes.io/managed-by: kf + app.kubernetes.io/name: weather + name: weather +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/component: app-server + app.kubernetes.io/managed-by: kf + app.kubernetes.io/name: weather + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + sidecar.istio.io/inject: "true" + traffic.sidecar.istio.io/includeOutboundIPRanges: '*' + creationTimestamp: null + labels: + app.kubernetes.io/component: app-server + app.kubernetes.io/managed-by: kf + app.kubernetes.io/name: weather + kf.dev/networkpolicy: app + spec: + containers: + - env: + - name: PORT + value: "8080" + - name: VCAP_APP_PORT + value: $(PORT) + - name: CF_INSTANCE_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: CF_INSTANCE_INTERNAL_IP + value: $(CF_INSTANCE_IP) + - name: VCAP_APP_HOST + value: $(CF_INSTANCE_IP) + - name: CF_INSTANCE_PORT + value: "8080" + - name: CF_INSTANCE_ADDR + value: $(CF_INSTANCE_IP):$(CF_INSTANCE_PORT) + - name: CF_INSTANCE_GUID + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.uid + - name: INSTANCE_GUID + value: $(CF_INSTANCE_GUID) + - name: CF_INSTANCE_INDEX + value: "0" + - name: INSTANCE_INDEX + value: $(CF_INSTANCE_INDEX) + - name: MEMORY_LIMIT + valueFrom: + resourceFieldRef: + divisor: 1Mi + resource: limits.memory + - name: DISK_LIMIT + valueFrom: + resourceFieldRef: + divisor: 1Mi + resource: limits.ephemeral-storage + - name: LANG + value: en_US.UTF-8 + - name: VCAP_SERVICES + valueFrom: + secretKeyRef: + key: VCAP_SERVICES + name: kf-injected-envs-weather + optional: false + - name: DATABASE_URL + valueFrom: + secretKeyRef: + key: DATABASE_URL + name: kf-injected-envs-weather + optional: true + - name: MEMORY_LIMIT + value: $(MEMORY_LIMIT)M + - name: KF_UPDATE_REQUESTS_22d1139e-aa64-485d-8bac-acc75574daa5 + value: "47" + image: northamerica-northeast1-docker.pkg.dev/arctiq-anthos/kf-processing/app_weather_weather-47:6764c547-c72e-4b20-b370-cf93dadcb61f + imagePullPolicy: Always + name: user-container + ports: + - containerPort: 8080 + name: http-user-port + protocol: TCP + readinessProbe: + failureThreshold: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 8080 + timeoutSeconds: 60 + resources: + requests: + cpu: 100m + ephemeral-storage: 1Gi + memory: 1Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + enableServiceLinks: false + restartPolicy: Always + schedulerName: default-scheduler + serviceAccount: sa-weather + serviceAccountName: sa-weather + terminationGracePeriodSeconds: 30 + diff --git a/config-management/namespaces/weather/namespace.yaml b/config-management/namespaces/weather/namespace.yaml new file mode 100644 index 0000000..c5a1c6f --- /dev/null +++ b/config-management/namespaces/weather/namespace.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + app.kubernetes.io/managed-by: kf + istio-injection: enabled + name: weather +spec: + finalizers: + - kubernetes + diff --git a/config-management/namespaces/weather/sa.yaml b/config-management/namespaces/weather/sa.yaml new file mode 100644 index 0000000..bb6ea1c --- /dev/null +++ b/config-management/namespaces/weather/sa.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: serviceaccount + app.kubernetes.io/managed-by: kf + app.kubernetes.io/name: weather + name: sa-weather +secrets: +- name: sa-weather-token-2nkk7 + diff --git a/config-management/namespaces/weather/service.yaml b/config-management/namespaces/weather/service.yaml new file mode 100644 index 0000000..e535ea0 --- /dev/null +++ b/config-management/namespaces/weather/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: service + app.kubernetes.io/managed-by: kf + app.kubernetes.io/name: weather + name: weather +spec: + ports: + - name: http-user-port + port: 80 + targetPort: 8080 + selector: + app.kubernetes.io/component: app-server + app.kubernetes.io/managed-by: kf + app.kubernetes.io/name: weather + diff --git a/config-management/namespaces/weather/vs.yaml b/config-management/namespaces/weather/vs.yaml new file mode 100644 index 0000000..e69de29 diff --git a/jenkins/pipeline.yaml b/jenkins/pipeline.yaml deleted file mode 100644 index f96f7ac..0000000 --- a/jenkins/pipeline.yaml +++ /dev/null @@ -1,258 +0,0 @@ -apiVersion: v1 -kind: BuildConfig -metadata: - name: arctiq-weather - labels: - name: arctiq-weather - annotations: - pipeline.alpha.openshift.io/uses: '[{"name": "arctiq-weather", "namespace": "", "kind": "DeploymentConfig"}]' -spec: - triggers: - - - type: GitHub - github: - secret: CwokaXwLfG63gBP3R0jV - - - type: Generic - generic: - secret: secret101 - runPolicy: Serial - source: - type: None - strategy: - type: JenkinsPipeline - jenkinsPipelineStrategy: - env: - - name: GITHUB_CREDS - value: github - - name: APP_NAME - value: weather - - name: DEV_PROJECT - value: dev - - name: QA_REGISTRY - value: docker://docker-registry-default.apps. - - name: QA_PROJECT - value: qa - - name: QA_TAG - value: promoteToQA - - name: PRODA_CLUSTER - value: https:// - - name: PRODA_REGISTRY - value: docker://docker-registry-default.apps. - - name: PRODA_TAG - value: promoteToProdA - - name: PRODA_PROJECT - value: weather - - name: PRODB_CLUSTER - value: https:// - - name: PRODB_TAG - value: promoteToProdB - - name: PRODB_PROJECT - value: weather - - name: PRODB_REGISTRY - value: docker://docker-registry-default.apps. - - jenkinsfile: |- - pipeline { - agent { - label 'jenkins-slave-image-mgmt' - } - stages { - stage('Create Build') { - when { - expression { - openshift.withCluster() { - openshift.withProject(env.DEV_PROJECT) { - return !openshift.selector("bc", "weather").exists(); - } - } - } - } - steps { - script { - openshift.withCluster() { - openshift.withProject(env.DEV_PROJECT) { - openshift.newBuild("openshift/nodejs~https://github.com/ArctiqTeam/weather-app.git", "--name=weather") - } - } - } - } - } - stage('Start Build') { - steps { - script { - openshift.withCluster() { - openshift.withProject(env.DEV_PROJECT) { - openshift.selector("bc", "weather").startBuild("--wait=true") - } - } - } - } - } - stage('Deploy Image in Dev') { - when { - expression { - openshift.withCluster() { - openshift.withProject(env.DEV_PROJECT) { - return !openshift.selector('dc', 'weather').exists() - } - } - } - } - steps { - script { - openshift.withCluster() { - openshift.withProject(env.DEV_PROJECT) { - def app = openshift.newApp("weather:latest", "--name=weather") - app.narrow("svc").expose(); - - def dc = openshift.selector("dc", "weather") - while (dc.object().spec.replicas != dc.object().status.availableReplicas) { - sleep 10 - } - openshift.set("triggers", "dc/weather", "--manual") - } - } - } - } - } - stage('Deploy DEV') { - steps { - script { - openshift.withCluster() { - openshift.withProject(env.DEV_PROJECT) { - openshift.selector("dc", "weather").rollout().latest(); - } - } - } - } - } - stage('Tag for QA') { - steps { - script { - openshift.withCluster() { - openshift.tag("${env.DEV_PROJECT}/weather:latest", "${env.QA_PROJECT}/weather:${QA_TAG}") - } - } - } - } - stage('Create QA') { - when { - expression { - openshift.withCluster() { - openshift.withProject(env.QA_PROJECT) { - return !openshift.selector('dc', 'weather').exists() - } - } - } - } - steps { - script { - openshift.withCluster() { - openshift.withProject(env.QA_PROJECT) { - def app = openshift.newApp("weather:${QA_TAG}", "--name=weather") - app.narrow("svc").expose(); - - def dc = openshift.selector("dc", "weather") - while (dc.object().spec.replicas != dc.object().status.availableReplicas) { - sleep 10 - } - openshift.set("triggers", "dc/weather", "--manual") - } - } - } - } - } - stage('Deploy QA') { - steps { - script { - openshift.withCluster() { - openshift.withProject(env.QA_PROJECT) { - openshift.selector("dc", "weather").rollout().latest(); - } - } - } - } - } - stage('Promote to PROD?') { - steps { - timeout(time:15, unit:'MINUTES') { - input message: "Promote to PROD?", ok: "Promote" - } - } - } - stage('Tag for Prod') { - steps { - script { - openshift.withCluster() { - openshift.tag("${env.QA_PROJECT}/weather:${QA_TAG}", "${env.QA_PROJECT}/weather:${PRODA_TAG}") - openshift.tag("${env.QA_PROJECT}/weather:${QA_TAG}", "${env.QA_PROJECT}/weather:${PRODB_TAG}") - } - } - } - } - stage('Copy Image to PROD A') { - steps { - script { - openshift.withCluster() { - openshift.withProject("${QA_PROJECT}") { - withCredentials([string(credentialsId: 'QA_CREDS', variable: 'QA_CREDS'), - string(credentialsId: 'PRODA_CREDS', variable: 'PRODA_CREDS')]) { - // call like this so we dont print credentials to logs - sh ''' - set -x - skopeo --debug copy --src-tls-verify=false --dest-tls-verify=false --src-creds="${QA_CREDS}" --dest-creds="${PRODA_CREDS}" "${QA_REGISTRY}/${QA_PROJECT}/weather:${PRODA_TAG}" "${PRODA_REGISTRY}/${PRODA_PROJECT}/weather:${PRODA_TAG}" - ''' - } - } - } - } - } - } - stage('Deploy Image in Prod A') { - steps { - script { - openshift.withCluster(env.PRODA_CLUSTER) { - openshift.withProject(env.PRODA_PROJECT) { - openshift.selector("dc", "weather").rollout().latest(); - slackSend (color: '#0eaf2b', message: "Success: Job - App is deployed in Prod A - check the weather here: http://weather.apps.") - } - } - } - } - } - stage('Copy Image to PROD B') { - steps { - script { - openshift.withCluster() { - openshift.withProject("${QA_PROJECT}") { - withCredentials([string(credentialsId: 'QA_CREDS', variable: 'QA_CREDS'), - string(credentialsId: 'PRODB_CREDS', variable: 'PRODB_CREDS')]) { - // call like this so we dont print credentials to logs - sh ''' - set -x - skopeo --debug copy --src-tls-verify=false --dest-tls-verify=false --src-creds="${QA_CREDS}" --dest-creds="${PRODB_CREDS}" "${QA_REGISTRY}/${QA_PROJECT}/weather:${PRODB_TAG}" "${PRODB_REGISTRY}/${PRODB_PROJECT}/weather:${PRODB_TAG}" - ''' - } - } - } - } - } - } - stage('Deploy Image in Prod B') { - steps { - script { - openshift.withCluster(env.PRODB_CLUSTER) { - openshift.withProject(env.PRODB_PROJECT) { - openshift.selector("dc", "weather").rollout().latest(); - slackSend (color: '#0eaf2b', message: "Success: Job - App is deployed in Prod B - check the weather here: http://weather.apps.") - } - } - } - } - } - } - } - output: - resources: - postCommit: diff --git a/manifest.yml b/manifest.yml new file mode 100644 index 0000000..d7986a5 --- /dev/null +++ b/manifest.yml @@ -0,0 +1,3 @@ +--- +applications: +- name: weather diff --git a/public/css/style.css b/public/css/style.css index b7f796e..07410f3 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -7,7 +7,8 @@ body { width: 800px; margin: 0 auto; font-family: 'Open Sans', sans-serif; - background-image: url("https://raw.githubusercontent.com/daniyalj/arctiq-mountain/master/weatherlogoblue.jpg"); + /* background-image: url("https://raw.githubusercontent.com/daniyalj/arctiq-mountain/master/weatherlogoblue.jpg"); */ + background-image: url("https://github.com/stewartshea/arctiq-backgrounds/raw/master/sf-bg-arctiq.jpg"); background-size: cover; -moz-background-size: cover; -o-background-size: cover; diff --git a/server.js b/server.js index 44ffd53..80954b4 100644 --- a/server.js +++ b/server.js @@ -18,7 +18,7 @@ app.get('/', function (req, res) { app.post('/', function (req, res) { let city = req.body.city; - let url = `http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}` + let url = `http://api.openweathermap.org/data/2.5/weather?q=${city}&units=imperial&appid=${apiKey}` request(url, function (err, response, body) { if(err){ @@ -28,7 +28,7 @@ app.post('/', function (req, res) { if(weather.main == undefined){ res.render('index', {weather: null, error: 'Error, please try again'}); } else { - let weatherText = `It's ${weather.main.temp} degrees in ${weather.name}!`; + let weatherText = `It's ${weather.main.temp} degrees farenheit in ${weather.name}!`; res.render('index', {weather: weatherText, error: null, hostname: hostname}); } }